Explorar o código

Merge pull request #9 from jkelleyrtp/jk/no-more-patch-machine

Remove patch machine
Jonathan Kelley %!s(int64=4) %!d(string=hai) anos
pai
achega
8c37e4947e
Modificáronse 61 ficheiros con 2891 adicións e 1880 borrados
  1. 2 0
      .vscode/spellright.dict
  2. 2 1
      Cargo.toml
  3. 58 0
      README.md
  4. 171 0
      docs/advanced-guides/custom-renderer.md
  5. 0 0
      docs/advanced-guides/liveview.md
  6. 0 30
      docs/posts/08-custom-renderer.md
  7. 176 0
      examples/antipatterns.rs
  8. 0 143
      examples/app.rs
  9. 1 7
      examples/listener.rs
  10. 56 0
      examples/reducer.rs
  11. 111 14
      examples/rsx_usage.rs
  12. 17 13
      examples/webview.rs
  13. 19 39
      notes/Parity.md
  14. 4 1
      notes/SOLVEDPROBLEMS.md
  15. 3 1
      notes/TODO.md
  16. 2 2
      packages/cli/Cargo.toml
  17. 1 1
      packages/cli/src/builder.rs
  18. 6 3
      packages/core-macro/examples/prop_test.rs
  19. 17 2
      packages/core-macro/src/props/mod.rs
  20. 26 18
      packages/core-macro/src/rsx/component.rs
  21. 6 0
      packages/core/.vscode/spellright.dict
  22. 11 5
      packages/core/Cargo.toml
  23. 38 29
      packages/core/README.md
  24. 41 7
      packages/core/architecture.md
  25. 4 2
      packages/core/examples/borrowed.rs
  26. 18 9
      packages/core/src/component.rs
  27. 25 6
      packages/core/src/debug_renderer.rs
  28. 601 489
      packages/core/src/diff.rs
  29. 32 24
      packages/core/src/events.rs
  30. 3 6
      packages/core/src/lib.rs
  31. 73 17
      packages/core/src/nodebuilder.rs
  32. 267 77
      packages/core/src/nodes.rs
  33. 0 695
      packages/core/src/patch.rs
  34. 80 67
      packages/core/src/virtual_dom.rs
  35. 30 0
      packages/html-namespace/examples/poc.rs
  36. 4 17
      packages/web/Cargo.toml
  37. 10 9
      packages/web/examples/basic.rs
  38. 9 10
      packages/web/examples/context.rs
  39. 1 0
      packages/web/examples/deep.rs
  40. 3 2
      packages/web/examples/demoday.rs
  41. 0 4
      packages/web/examples/derive.rs
  42. 11 11
      packages/web/examples/events.rs
  43. 184 0
      packages/web/examples/framework_benchmark.rs
  44. 10 1
      packages/web/examples/hello.rs
  45. 1 0
      packages/web/examples/helloworld.rs
  46. 4 5
      packages/web/examples/infer.rs
  47. 7 14
      packages/web/examples/input.rs
  48. 1 2
      packages/web/examples/jackjill.rs
  49. 1 1
      packages/web/examples/landingpage.rs
  50. 1 1
      packages/web/examples/list.rs
  51. 28 0
      packages/web/examples/listy.rs
  52. 1 0
      packages/web/examples/many.rs
  53. 13 0
      packages/web/examples/props.rs
  54. 16 15
      packages/web/examples/rsxt.rs
  55. 8 7
      packages/web/examples/todomvc/main.rs
  56. 10 12
      packages/web/examples/todomvc_simple.rs
  57. 1 1
      packages/web/examples/todomvcsingle.rs
  58. 52 51
      packages/web/src/lib.rs
  59. 604 0
      packages/web/src/new.rs
  60. 0 1
      packages/web/src/old/interpreter.rs
  61. 10 8
      src/lib.rs

+ 2 - 0
.vscode/spellright.dict

@@ -57,3 +57,5 @@ dom
 textarea
 noderefs
 wasm
+7ns
+attr

+ 2 - 1
Cargo.toml

@@ -52,7 +52,8 @@ members = [
     "packages/core-macro",
     "packages/core",
     "packages/html-namespace",
-    # "packages/web",
+    "packages/web",
+    # "packages/cli",
     # "packages/atoms",
     # "packages/ssr",
     # "packages/docsite",

+ 58 - 0
README.md

@@ -89,3 +89,61 @@ TypeScript is a great addition to JavaScript, but comes with a lot of tweaking f
 - various macros (`html!`, `rsx!`) for fast template iteration
 
 And much more. Dioxus makes Rust apps just as fast to write as React apps, but affords more robustness, giving your frontend team greater confidence in making big changes in shorter time. Dioxus also works on the server, on the web, on mobile, on desktop - and it runs completely natively so performance is never an issue.
+
+# Parity with React
+
+Dioxus is heavily inspired by React, but we want your transition to feel like an upgrade. Dioxus is _most_ of the way there, but missing a few key features. This parity table does not necessarily include important ecosystem crates like code blocks, markdown, resizing hooks, etc.
+
+### Phase 1: The Basics
+
+| Feature                | Dioxus | React | Notes for Dioxus                                 |
+| ---------------------- | ------ | ----- | ------------------------------------------------ |
+| Conditional Rendering  | ✅     | ✅    | if/then to hide/show component                   |
+| Map, Iterator          | ✅     | ✅    | map/filter/reduce rsx!                           |
+| Keyed Components       | ✅     | ✅    | advanced diffing with keys                       |
+| Web                    | ✅     | ✅    | renderer for web browser                         |
+| Desktop (webview)      | ✅     | ✅    | renderer for desktop                             |
+| Context                | ✅     | ✅    | share state through the tree                     |
+| Hook                   | ✅     | ✅    | memory cells in components                       |
+| SSR                    | ✅     | ✅    | render directly to string                        |
+| Runs natively          | ✅     | ❓    | runs as a portable binary w/o a runtime (Node)   |
+| Component Children     | ✅     | ✅    | cx.children() as a list of nodes                 |
+| Null components        | ✅     | ✅    | allow returning no components                    |
+| No-div components      | ✅     | ✅    | components that render components                |
+| Fragments              | ✅     | ✅    | rsx! can return multiple elements without a root |
+| Manual Props           | ✅     | ✅    | Manually pass in props with spread syntax        |
+| Controlled Inputs      | ✅     | ✅    | stateful wrappers around inputs                  |
+| 1st class global state | 🛠      | ✅    | redux/recoil/mobx on top of context              |
+| NodeRef                | 🛠      | ✅    | gain direct access to nodes [1]                  |
+| CSS/Inline Styles      | 🛠      | ✅    | syntax for inline styles/attribute groups[2]     |
+
+- [1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API.
+- [2] Would like to solve this in a more general way. Something like attribute groups that's not styling-specific.
+
+### Phase 2: Advanced Toolkits
+
+| Feature               | Dioxus | React | Notes for Dioxus                           |
+| --------------------- | ------ | ----- | ------------------------------------------ |
+| 1st class router      | 👀     | ✅    | Hook built on top of history               |
+| Assets                | 👀     | ✅    | include css/svg/img url statically         |
+| Integrated classnames | 🛠      | ❓    | built-in `classnames`                      |
+| Suspense              | 👀     | 🛠     | schedule future render from future/promise |
+| Transition            | 👀     | 🛠     | High-level control over suspense           |
+| Animation             | 👀     | ✅    | Spring-style animations                    |
+| Mobile                | 👀     | ✅    | Render with cacao                          |
+| Desktop (native)      | 👀     | ✅    | Render with native desktop                 |
+| 3D Renderer           | 👀     | ✅    | react-three-fiber                          |
+
+### Phase 3: Additional Complexity
+
+| Feature              | Dioxus | React | Notes for Dioxus                     |
+| -------------------- | ------ | ----- | ------------------------------------ |
+| Portal               | ❓     | ✅    | cast elements through tree           |
+| Error/Panic boundary | ❓     | ✅    | catch panics and display custom BSOD |
+| Code-splitting       | 👀     | ✅    | Make bundle smaller/lazy             |
+| LiveView             | 👀     | ❓    | Example for SSR + WASM apps          |
+
+- ✅ = implemented and working
+- 🛠 = actively being worked on
+- 👀 = not yet implemented or being worked on
+- ❓ = not sure if will or can implement

+ 171 - 0
docs/advanced-guides/custom-renderer.md

@@ -0,0 +1,171 @@
+# Custom Renderer
+
+Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer.
+
+Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but only really require implementation of the `RealDom` trait for things to function properly.
+
+## The specifics:
+
+Implementing the renderer is fairly straightforward. The renderer needs to:
+
+1. Handle the stream of edits generated by updates to the virtual DOM
+2. Register listeners and pass events into the virtual DOM's event system
+3. Progress the virtual DOM with an async executor (or disable the suspense API and use `progress_sync`)
+
+Essentially, your renderer needs to implement the `RealDom` trait and generate `EventTrigger` objects to update the VirtualDOM. From there, you'll have everything needed to render the VirtualDOM to the screen.
+
+Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves.
+
+For reference, check out the WebSys renderer as a starting point for your custom renderer.
+
+## Trait implementation
+
+The current `RealDom` trait lives in `dioxus_core/diff`. A version of it is provided here:
+
+```rust
+pub trait RealDom {
+    // Navigation
+    fn push_root(&mut self, root: RealDomNode);
+
+    // Add Nodes to the dom
+    fn append_child(&mut self);
+    fn replace_with(&mut self);
+
+    // Remove Nodes from the dom
+    fn remove(&mut self);
+    fn remove_all_children(&mut self);
+
+    // Create
+    fn create_text_node(&mut self, text: &str) -> RealDomNode;
+    fn create_element(&mut self, tag: &str, namespace: Option<&str>) -> RealDomNode;
+
+    // Events
+    fn new_event_listener(
+        &mut self,
+        event: &str,
+        scope: ScopeIdx,
+        element_id: usize,
+        realnode: RealDomNode,
+    );
+    fn remove_event_listener(&mut self, event: &str);
+
+    // modify
+    fn set_text(&mut self, text: &str);
+    fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool);
+    fn remove_attribute(&mut self, name: &str);
+
+    // node ref
+    fn raw_node_as_any_mut(&self) -> &mut dyn Any;
+}
+```
+
+This trait defines what the Dioxus VirtualDOM expects a "RealDom" abstraction to implement for the Dioxus diffing mechanism to function properly. The Dioxus diffing mechanism operates as a [stack machine](https://en.wikipedia.org/wiki/Stack_machine) where the "push_root" method pushes a new "real" DOM node onto the stack and "append_child" and "replace_with" both remove nodes from the stack. When the RealDOM creates new nodes, it must return the `RealDomNode` type... which is just an abstraction over u32. We strongly recommend the use of `nohash-hasher`'s IntMap for managing the mapping of `RealDomNode` (ids) to their corresponding real node. For an IntMap of 1M+ nodes, an index time takes about 7ns which is very performant when compared to the traditional hasher.
+
+## Event loop
+
+Like most GUIs, Dioxus relies on an event loop to progress the VirtualDOM. The VirtualDOM itself can produce events as well, so it's important that your custom renderer can handle those too.
+
+The code for the WebSys implementation is straightforward, so we'll add it here to demonstrate how simple an event loop is:
+
+```rust
+pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
+    // Push the body element onto the WebsysDom's stack machine
+    let mut websys_dom = crate::new::WebsysDom::new(prepare_websys_dom().first_child().unwrap());
+    websys_dom.stack.push(root_node);
+
+    // Rebuild or hydrate the virtualdom
+    self.internal_dom.rebuild(&mut websys_dom)?;
+
+    // Wait for updates from the real dom and progress the virtual dom
+    while let Some(trigger) = websys_dom.wait_for_event().await {
+        websys_dom.stack.push(body_element.first_child().unwrap());
+        self.internal_dom
+            .progress_with_event(&mut websys_dom, trigger)?;
+    }
+}
+```
+
+It's important that you decode the real events from your event system into Dioxus' synthetic event system (synthetic meaning abstracted). This simply means matching your event type and creating a Dioxus `VirtualEvent` type. Your custom event must implement the corresponding event trait:
+
+```rust
+fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
+    match event.type_().as_str() {
+        "keydown" | "keypress" | "keyup" => {
+            struct CustomKeyboardEvent(web_sys::KeyboardEvent);
+            impl dioxus::events::KeyboardEvent for CustomKeyboardEvent {
+                fn char_code(&self) -> usize { self.0.char_code() }
+                fn ctrl_key(&self) -> bool { self.0.ctrl_key() }
+                fn key(&self) -> String { self.0.key() }
+                fn key_code(&self) -> usize { self.0.key_code() }
+                fn locale(&self) -> String { self.0.locale() }
+                fn location(&self) -> usize { self.0.location() }
+                fn meta_key(&self) -> bool { self.0.meta_key() }
+                fn repeat(&self) -> bool { self.0.repeat() }
+                fn shift_key(&self) -> bool { self.0.shift_key() }
+                fn which(&self) -> usize { self.0.which() }
+                fn get_modifier_state(&self, key_code: usize) -> bool { self.0.get_modifier_state() }
+            }
+            VirtualEvent::KeyboardEvent(Rc::new(event.clone().dyn_into().unwrap()))
+        }
+        _ => todo!()
+```
+
+## Custom raw elements
+
+If you need to go as far as relying on custom elements for your renderer - you totally can. This still enables you to use Dioxus' reactive nature, component system, shared state, and other features, but will ultimately generate different nodes. All attributes and listeners for the HTML and SVG namespace are shuttled through helper structs that essentially compile away (pose no runtime overhead). You can drop in your own elements any time you want, with little hassle. However, you must be absolutely sure your renderer can handle the new type, or it will crash and burn.
+
+For example, the `div` element is (approximately!) defined as such:
+
+```rust
+struct div(NodeBuilder);
+impl<'a> div {
+    #[inline]
+    fn new(factory: &NodeFactory<'a>) -> Self {
+        Self(factory.new_element("div"))
+    }
+    #[inline]
+    fn onclick(mut self, callback: impl Fn(MouseEvent) + 'a) -> Self {
+        self.0.add_listener("onclick", |evt: VirtualEvent| match evt {
+            MouseEvent(evt) => callback(evt),
+            _ => {}
+        });
+        self
+    }
+    // etc
+}
+```
+
+The rsx! and html! macros just use the `div` struct as a compile-time guard around the NodeFactory.
+
+## Compatibility
+
+Forewarning: not every hook and service will work on your platform. Dioxus wraps things that need to be cross-platform in "synthetic" types. However, downcasting to a native type might fail if the types don't match.
+
+There are three opportunities for platform incompatibilities to break your program:
+
+1. When downcasting elements via `Ref.to_native<T>()`
+2. When downcasting events via `Event.to_native<T>()`
+3. Calling platform-specific APIs that don't exist
+
+The best hooks will properly detect the target platform and still provide functionality, failing gracefully when a platform is not supported. We encourage - and provide - an indication to the user on what platforms a hook supports. For issues 1 and 2, these return a result as to not cause panics on unsupported platforms. When designing your hooks, we recommend propagating this error upwards into user facing code, making it obvious that this particular service is not supported.
+
+This particular code _will panic_ due to the unwrap. Try to avoid these types of patterns.
+
+```rust
+let div_ref = use_node_ref(&cx);
+
+cx.render(rsx!{
+    div { ref: div_ref, class: "custom class",
+        button { "click me to see my parent's class"
+            onclick: move |_| if let Some(div_ref) = div_ref {
+                panic!("Div class is {}", div_ref.to_native::<web_sys::Element>().unwrap().class())
+            }
+        }
+    }
+})
+
+```
+
+## Conclusion
+
+That should be it! You should have nearly all the knowledge required on how to implement your own renderer. We're super interested in seeing Dioxus apps brought to custom desktop renderers, mobile renderer, video game UI, and even augmented reality! If you're interesting in contributing to any of the these projects, don't be afraid to reach out or join the community.

+ 0 - 0
docs/posts/12-liveview.md → docs/advanced-guides/liveview.md


+ 0 - 30
docs/posts/08-custom-renderer.md

@@ -1,30 +0,0 @@
-# Custom Renderer
-
-Dioxus is an incredibly portable framework for UI development. The lessons, knowledge, hooks, and components you acquire over time can always be used for future projects. However, sometimes those projects cannot leverage a supported renderer or you need to implement your own better renderer.
-
-Great news: the design of the renderer is entirely up to you! We provide suggestions and inspiration with the 1st party renderers, but provide no trait or explicit interface to follow. 
-
-Implementing the renderer is fairly straightforward. The renderer needs to:
-1. Handle the stream of edit events generated by updates to the virtual DOM
-2. Register listeners and pass events into the virtual DOM's event system
-3. Progress the virtual DOM with an async executor (or disable the suspense API and use `progress_sync`)
-
-Essentially, your renderer needs to understand the EditEvent type and provide a callback for injecting events. From there, you'll have everything needed to render the VirtualDOM to the screen.
-
-Internally, Dioxus handles the tree relationship, diffing, memory management, and the event system, leaving as little as possible required for renderers to implement themselves.
-
-For inspiration, check out the source code for the various renderers we support:
-- WebSys
-- Morph
-
-## Compatibility 
-
-Forewarning: not every hook and service will work on your platform. Dioxus wraps things that need to be cross-platform in "synthetic" types. However, downcasting to a native type might fail if the types don't match. 
-
-There are three opportunities for platform incompatibilities to break your program:
-1. When downcasting elements via `Ref.to_native<T>()`
-2. When downcasting events via `Event.to_native<T>()`
-3. Calling platform-specific APIs that don't exist
-
-The best hooks will properly detect the target platform and still provide functionality, failing gracefully when a platform is not supported. We encourage - and provide - an indication to the user on what platforms a hook supports. For issues 1 and 2, these return a result as to not cause panics on unsupported platforms. When designing your hooks, we recommend propagating this error upwards into user facing code, making it obvious that this particular service is not supported.
-

+ 176 - 0
examples/antipatterns.rs

@@ -0,0 +1,176 @@
+//! Example: Antipatterns
+//! ---------------------
+//!
+//! This example shows what *not* to do and provides a reason why a given pattern is considered an "AntiPattern". Most
+//! anti-patterns are considered wrong to due performance reasons or violate the "rules" of Dioxus. These rules are
+//! borrowed from other successful UI frameworks, and Dioxus is more focused on providing a familiar, ergonomic interface
+//! rather than building new harder-to-misuse patterns.
+//!
+//! In this list we showcase:
+//! - Not adding keys for iterators
+//! - Heavily nested fragments
+//! - Understadning ordering of set_state
+//! - Naming conventions
+//! - Rules of hooks
+//!
+//! Feel free to file a PR or Issue if you run into another antipattern that you think users of Dioxus should know about.
+use dioxus::prelude::*;
+
+fn main() {}
+
+/// Antipattern: Iterators without keys
+/// -----------------------------------
+///
+/// This is considered an anti-pattern for performance reasons. Dioxus must diff your current and old layout and must
+/// take a slower path if it can't correlate old elements with new elements. Lists are particularly susceptible to the
+/// "slow" path, so you're strongly encouraged to provide a unique ID stable between renders. Additionally, providing
+/// the *wrong* keys is even worse. Keys should be:
+/// - Unique
+/// - Stable
+/// - Predictable
+///
+/// Dioxus will log an error in the console if it detects that your iterator does not properly generate keys
+#[derive(PartialEq, Props)]
+struct NoKeysProps {
+    data: std::collections::HashMap<u32, String>,
+}
+static AntipatternNoKeys: FC<NoKeysProps> = |cx| {
+    // WRONG: Make sure to add keys!
+    rsx!(in cx, ul {
+        {cx.data.iter().map(|(k, v)| rsx!(li { "List item: {v}" }))}
+    });
+    // Like this:
+    rsx!(in cx, ul {
+        {cx.data.iter().map(|(k, v)| rsx!(li { key: "{k}", "List item: {v}" }))}
+    })
+};
+
+/// Antipattern: Deeply nested fragments
+/// ------------------------------------
+///
+/// This particular antipattern is not necessarily an antipattern in other frameworks but does has a performance impact
+/// in Dioxus apps. Fragments don't mount a physical element to the dom immediately, so Dioxus must recurse into its
+/// children to find a physical dom node. This process is called "normalization". Other frameworks perform an agressive
+/// mutative normalization while Dioxus keeps your VNodes immutable. This means that deepely nested fragments make Dioxus
+/// perform unnecessary work. Prefer one or two levels of fragments / nested components until presenting a true dom element.
+///
+/// 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| {
+    // Try to avoid heavily nesting fragments
+    rsx!(in cx,
+        Fragment {
+            Fragment {
+                Fragment {
+                    Fragment {
+                        Fragment {
+                            div { "Finally have a real node!" }
+                        }
+                    }
+                }
+            }
+        }
+    )
+};
+
+/// Antipattern: Using state after its been updated
+/// -----------------------------------------------
+///
+/// This is an antipattern in other frameworks, but less so in Dioxus. However, it's important to highlight that use_state
+/// does *not* work the same way as it does in React. Rust provides explicit guards against mutating shared data - a huge
+/// problem in JavaScript land. With Rust and Dioxus, it's nearly impossible to misuse `use_state` - you simply can't
+/// accidentally modify the state you've received!
+///
+/// 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 AntipaternRelyingOnSetState: FC<()> = |cx| {
+    let (state, set_state) = use_state(&cx, || "Hello world");
+    set_state("New state");
+    // This will return false! `state` will *still* be "Hello world"
+    assert!(state == &"New state");
+    todo!()
+};
+
+/// Antipattern: Capitalization
+/// ---------------------------
+///
+/// This antipattern is enforced to retain parity with other frameworks and provide useful IDE feedback, but is less
+/// critical than other potential misues. In short:
+/// - Only raw elements may start with a lowercase character
+/// - All components must start with an uppercase character
+///
+/// IE: the following component will be rejected when attempted to be used in the rsx! macro
+static antipattern_component: FC<()> = |cx| todo!();
+
+/// Antipattern: Misusing hooks
+/// ---------------------------
+///
+/// This pattern is an unfortunate one where Dioxus supports the same behavior as in other frameworks. Dioxus supports
+/// "hooks" - IE "memory cells" that allow a value to be stored between renders. This allows other hooks to tap into
+/// a components "memory" without explicitly adding all of its data to a struct definition. In Dioxus, hooks are allocated
+/// with a bump arena and then immediately sealed.
+///
+/// This means that hooks may not be misued:
+/// - Called out of order
+/// - Called in a conditional
+/// - Called in loops or callbacks
+///
+/// For the most part, Rust helps with rule #3 but does not save you from misusing rule #1 or #2. Dioxus will panic
+/// if hooks do not downcast the same data between renders. This is validated by TypeId - and eventually - a custom key.
+#[derive(PartialEq, Props)]
+struct MisuedHooksProps {
+    should_render_state: bool,
+}
+static AntipatternMisusedHooks: FC<MisuedHooksProps> = |cx| {
+    if cx.should_render_state {
+        // do not place a hook in the conditional!
+        // prefer to move it out of the conditional
+        let (state, set_state) = use_state(&cx, || "hello world");
+        rsx!(in cx, div { "{state}" })
+    } else {
+        rsx!(in cx, div { "Not rendering state" })
+    }
+};
+
+/// Antipattern: Downcasting refs and panicing
+/// ------------------------------------------
+///
+/// Occassionally it's useful to get the ref of an element to handle it directly. Elements support downcasting to
+/// Dioxus's virtual element types as well as their true native counterparts. Downcasting to Dioxus' virtual elements
+/// will never panic, but downcasting to native elements will fail if on an unsupported platform. We recommend avoiding
+/// publishing hooks and components that deply rely on control over elements using their native `ref`, preferring to
+/// use their Dioxus Virtual Element counterpart instead.
+// This particular code *will panic* due to the unwrap. Try to avoid these types of patterns.
+/// ---------------------------------
+/// TODO: Get this to compile properly
+/// let div_ref = use_node_ref(&cx);
+///
+/// cx.render(rsx!{
+///     div { ref: div_ref, class: "custom class",
+///         button { "click me to see my parent's class"
+///             onclick: move |_| if let Some(div_ref) = div_ref {
+///                 panic!("Div class is {}", div_ref.to_native::<web_sys::Element>().unwrap().class())
+///             }
+///         }
+///     }
+/// })
+static _example: FC<()> = |cx| todo!();
+
+/// Antipattern: publishing components and hooks with all features enabled
+/// ----------------------------------------------------------------------
+///
+/// The `dioxus` crate combines a bunch of useful utilities together (like the rsx! and html! macros, hooks, and more).
+/// However, when publishing your custom hook or component, we highly advise using only the `core` feature on the dioxus
+/// crate. This makes your crate compile faster, makes it more stable, and avoids bringing in incompatible libraries that
+/// might make it not compile on unsupported platforms.
+///
+/// We don't have a code snippet for this, but just prefer to use this line:
+///     dioxus = { version = "*", features = ["core"]}
+/// instead of this one:
+///     dioxus = { version = "*", features = ["web", "desktop", "full"]}
+/// in your Cargo.toml
+///
+/// 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| todo!();

+ 0 - 143
examples/app.rs

@@ -1,143 +0,0 @@
-#![allow(unused)]
-/*
-This example shows how to encapsulate sate in dioxus components with the reducer pattern.
-This pattern is very useful when a single component can handle many types of input that can
-be represented by an enum. This particular pattern is very powerful in rust where ADTs can simplify
-much of the traditional reducer boilerplate.
-*/
-
-fn main() {}
-use dioxus::prelude::*;
-use std::future::Future;
-
-enum Actions {
-    Pause,
-    Play,
-}
-
-struct SomeState {
-    is_playing: bool,
-}
-
-impl SomeState {
-    fn new() -> Self {
-        Self { is_playing: false }
-    }
-    fn reduce(&mut self, action: Actions) {
-        match action {
-            Actions::Pause => self.is_playing = false,
-            Actions::Play => self.is_playing = true,
-        }
-    }
-    fn is_playing(&self) -> &'static str {
-        match self.is_playing {
-            true => "currently playing!",
-            false => "not currently playing",
-        }
-    }
-}
-
-pub static ExampleReducer: FC<()> = |ctx| {
-    let (state, reduce) = use_reducer(&ctx, SomeState::new, SomeState::reduce);
-
-    let is_playing = state.is_playing();
-
-    ctx.render(rsx! {
-        div {
-            h1 {"Select an option"}
-            h3 {"The radio is... {is_playing}!"}
-            button {
-                "Pause"
-                onclick: move |_| reduce(Actions::Pause)
-            }
-            button {
-                "Play"
-                onclick: move |_| reduce(Actions::Play)
-            }
-        }
-    })
-};
-
-/*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-*/
-
-struct AppContext {
-    name: String,
-}
-
-enum KindaState {
-    Ready,
-    Complete,
-    Erred,
-}
-
-static EnumReducer: FC<()> = |ctx| {
-    let (state, reduce) = use_reducer(&ctx, || KindaState::Ready, |cur, new| *cur = new);
-
-    let status = match state {
-        KindaState::Ready => "Ready",
-        KindaState::Complete => "Complete",
-        KindaState::Erred => "Erred",
-    };
-
-    ctx.render(rsx! {
-        div {
-            h1 {"{status}"}
-            button {
-                "Set Ready"
-                onclick: move |_| reduce(KindaState::Ready)
-            }
-            button {
-                "Set Complete"
-                onclick: move |_| reduce(KindaState::Complete)
-            }
-            button {
-                "Set Erred"
-                onclick: move |_| reduce(KindaState::Erred)
-            }
-            ul {
-                {(0..10).map(|f| {
-
-                    rsx!{
-                        li {
-                            "hello there!"
-                        }
-                    }
-                })}
-            }
-        }
-    })
-};
-
-/// Demonstrate how the DebugRenderer can be used to unit test components without needing a browser
-/// These tests can run locally.
-/// They use the "compare" method of the debug renderer to do partial tree compares for succint
-#[test]
-fn ensure_it_works_properly() -> dioxus::error::Result<()> {
-    let mut test = DebugRenderer::new(EnumReducer);
-    test.compare(rsx! { div { h1 {"Ready"} } })?;
-
-    test.trigger_listener(1)?;
-    test.compare(rsx! { div { h1 {"Ready"} } })?;
-
-    test.trigger_listener(2)?;
-    test.compare(rsx! { div { h1 {"Complete"} } })?;
-
-    test.trigger_listener(3)?;
-    test.compare(rsx! { div { h1 {"Erred"} } })?;
-    Ok(())
-}

+ 1 - 7
examples/listener.rs

@@ -2,13 +2,7 @@
 
 use dioxus_core::prelude::*;
 
-fn main() {
-    Some(10)
-        .map(|f| f * 5)
-        .map(|f| f / 3)
-        .map(|f| f * 5)
-        .map(|f| f / 3);
-}
+fn main() {}
 
 static Example: FC<()> = |ctx| {
     let (name, set_name) = use_state(&ctx, || "...?");

+ 56 - 0
examples/reducer.rs

@@ -0,0 +1,56 @@
+//! Example: Reducer Pattern
+//! -----------------
+//! This example shows how to encapsulate sate in dioxus components with the reducer pattern.
+//! This pattern is very useful when a single component can handle many types of input that can
+//! be represented by an enum.
+
+fn main() {}
+use dioxus::prelude::*;
+
+pub static ExampleReducer: FC<()> = |ctx| {
+    let (state, reduce) = use_reducer(&ctx, PlayerState::new, PlayerState::reduce);
+
+    let is_playing = state.is_playing();
+
+    ctx.render(rsx! {
+        div {
+            h1 {"Select an option"}
+            h3 {"The radio is... {is_playing}!"}
+            button {
+                "Pause"
+                onclick: move |_| reduce(PlayerAction::Pause)
+            }
+            button {
+                "Play"
+                onclick: move |_| reduce(PlayerAction::Play)
+            }
+        }
+    })
+};
+
+enum PlayerAction {
+    Pause,
+    Play,
+}
+
+struct PlayerState {
+    is_playing: bool,
+}
+
+impl PlayerState {
+    fn new() -> Self {
+        Self { is_playing: false }
+    }
+    fn reduce(&mut self, action: PlayerAction) {
+        match action {
+            PlayerAction::Pause => self.is_playing = false,
+            PlayerAction::Play => self.is_playing = true,
+        }
+    }
+    fn is_playing(&self) -> &'static str {
+        match self.is_playing {
+            true => "currently playing!",
+            false => "not currently playing",
+        }
+    }
+}

+ 111 - 14
examples/rsx_usage.rs

@@ -5,10 +5,6 @@
 //!
 //! A full in-depth reference guide is available at: https://www.notion.so/rsx-macro-basics-ef6e367dec124f4784e736d91b0d0b19
 //!
-//! ## Topics
-//!
-//!
-//!
 //! ### Elements
 //! - Create any element from its tag
 //! - Accept compile-safe attributes for each tag
@@ -46,23 +42,119 @@ fn main() {
     dioxus::webview::launch(Example);
 }
 
+/// When trying to return "nothing" to Dioxus, you'll need to specify the type parameter or Rust will be sad.
+/// This type alias specifices the type for you so you don't need to write "None as Option<()>"
+const NONE_ELEMENT: Option<()> = None;
+
 use baller::Baller;
 use dioxus_core::prelude::*;
 
-static Example: FC<()> = |ctx| {
-    ctx.render(rsx! {
+static Example: FC<()> = |cx| {
+    let formatting = "formatting!";
+    let formatting_tuple = ("a", "b");
+    let lazy_fmt = format_args!("lazily formatted text");
+    cx.render(rsx! {
         div {
             // Elements
+            div {}
+            h1 {"Some text"}
+            h1 {"Some text with {formatting}"}
+            h1 {"Formatting basic expressions {formatting_tuple.0} and {formatting_tuple.1}"}
+            h2 {
+                "Multiple"
+                "Text"
+                "Blocks"
+                "Use comments as separators in html"
+            }
+            div {
+                h1 {"multiple"}
+                h2 {"nested"}
+                h3 {"elements"}
+            }
+            div {
+                class: "my special div"
+                h1 {"Headers and attributes!"}
+            }
+            div {
+                // pass simple rust expressions in
+                class: lazy_fmt,
+                id: format_args!("attributes can be passed lazily with std::fmt::Arguments"),
+                div {
+                    class: {
+                        const WORD: &str = "expressions";
+                        format_args!("Arguments can be passed in through curly braces for complex {}", WORD)
+                    }
+                }
+            }
+
+            // Expressions can be used in element position too:
+            {rsx!(p { "More templating!" })}
+            {html!(<p>"Even HTML templating!!"</p>)}
+
+            // Iterators
+            {(0..10).map(|i| rsx!(li { "{i}" }))}
+            {{
+                let data = std::collections::HashMap::<&'static str, &'static str>::new();
+                // Iterators *should* have keys when you can provide them.
+                // Keys make your app run faster. Make sure your keys are stable, unique, and predictable.
+                // Using an "ID" associated with your data is a good idea.
+                data.into_iter().map(|(k, v)| rsx!(li { key: "{k}" "{v}" }))
+            }}
+            
 
+            // 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* unhygenic macros)
+            {match true {
+                true => rsx!(in cx, h1 {"Top text"}),
+                false => cx.render(rsx!( h1 {"Bottom text"}))
+            }}
 
+            // Conditional rendering
+            // Dioxus conditional rendering is based around None/Some. We have no special syntax for conditionals.
+            // You can convert a bool condition to rsx! with .then and .or
+            {true.then(|| rsx!(div {}))}
 
+            // True conditions need to be rendered (same reasons as matching)
+            {if true {
+                rsx!(in cx, h1 {"Top text"})
+            } else {
+                cx.render(rsx!( h1 {"Bottom text"}))
+            }}
 
-            // ==============
-            //   Components
-            // ==============
+            // returning "None" is a bit noisy... but rare in practice
+            {None as Option<()>}
+
+            // Use the Dioxus type-alias for less noise
+            {NONE_ELEMENT}
+
+            // can also just use empty fragments
+            Fragment {}
+
+            // Fragments let you insert groups of nodes without a parent.
+            // This lets you make components that insert elements as siblings without a container.
+            div {"A"}
+            Fragment {
+                div {"B"}
+                div {"C"}
+                Fragment {
+                    "D"
+                    Fragment {
+                        "heavily nested fragments is an antipattern"
+                        "they cause Dioxus to do unnecessary work"
+                        "don't use them carelessly if you can help it"
+                    }
+                }
+            }
+            
+
+            // Components
             // Can accept any paths
-            crate::baller::Baller {}
+            // Notice how you still get syntax highlighting and IDE support :)
+            Baller {}
             baller::Baller { }
+            crate::baller::Baller {}
 
             // Can take properties
             Taller { a: "asd" }
@@ -70,11 +162,14 @@ static Example: FC<()> = |ctx| {
             // Can take optional properties
             Taller { a: "asd" }
 
-            // Can pass in props directly
-            Taller { a: "asd" /* ..{props}*/  }
+            // Can pass in props directly as an expression
+            {{
+                let props = TallerProps {a: "hello"};
+                rsx!(Taller { ..props })
+            }}
 
             // Can take children
-            Taller { a: "asd", div {} }
+            Taller { a: "asd", div {"hello world!"} }
         }
     })
 };
@@ -83,7 +178,8 @@ mod baller {
     use super::*;
     pub struct BallerProps {}
 
-    pub fn Baller(ctx: Context<()>) -> VNode {
+    /// This component totally balls
+    pub fn Baller(cx: Context<()>) -> VNode {
         todo!()
     }
 }
@@ -93,6 +189,7 @@ pub struct TallerProps {
     a: &'static str,
 }
 
+/// This component is taller than most :)
 pub fn Taller(ctx: Context<TallerProps>) -> VNode {
     let b = true;
     todo!()

+ 17 - 13
examples/webview.rs

@@ -6,22 +6,26 @@
 //! Under the hood, the dioxus_webview crate bridges a native Dioxus VirtualDom with a custom prebuit application running
 //! in the webview runtime. Custom handlers are provided for the webview instance to consume patches and emit user events
 //! into the native VDom instance.
+//!
+//! Currently, NodeRefs won't work properly, but all other event functionality will.
 
 use dioxus::prelude::*;
 
 fn main() {
-    dioxus::webview::launch(|ctx| {
-        let (count, set_count) = use_state(&ctx, || 0);
+    dioxus::webview::launch(App);
+}
 
-        ctx.render(rsx! {
-            div {
-                h1 { "Dioxus Desktop Demo" }
-                p { "Count is {count}" }
-                button {
-                    "Click to increment"
-                    onclick: |_| set_count(count + 1)
-                }
+static App: FC<()> = |cx| {
+    let (count, set_count) = use_state(&cx, || 0);
+
+    cx.render(rsx! {
+        div {
+            h1 { "Dioxus Desktop Demo" }
+            p { "Count is {count}" }
+            button {
+                "Click to increment"
+                onclick: move |_| set_count(count + 1)
             }
-        })
-    });
-}
+        }
+    })
+};

+ 19 - 39
notes/Parity.md

@@ -1,51 +1,31 @@
 # Parity with React
 
-Sorted by priority
-
-| Feature                | Dioxus | React | Notes                                            |
-| ---------------------- | ------ | ----- | ------------------------------------------------ |
-| ----- Phase 1 -----    | -----  | ----- | -----                                            |
-| Conditional Rendering  | ✅     | ✅    | if/then to hide/show component                   |
-| Map, Iterator          | ✅     | ✅    | map/filter/reduce rsx!                           |
-| Keyed Components       | ✅     | ✅    | advanced diffing with keys                       |
-| Web                    | ✅     | ✅    | renderer for web browser                         |
-| Desktop (webview)      | ✅     | ✅    | renderer for desktop                             |
-| Context                | ✅     | ✅    | share state through the tree                     |
-| Hook                   | ✅     | ✅    | memory cells in components                       |
-| SSR                    | ✅     | ✅    | render directly to string                        |
-| Runs natively          | ✅     | 👀    | runs as a portable binary w/ extra tooling       |
-| Component Children     | ✅     | ✅    | cx.children() as a list of nodes                 |
-| Null components        | ✅     | ✅    | allow returning no components                    |
-| No-div components      | ✅     | ✅    | components that render components                |
-| Fragments              | ✅     | ✅    | rsx! can return multiple elements without a root |
-| NodeRef                | 👀     | ✅    | gain direct access to nodes                      |
-| Controlled Inputs      | ✅     | ✅    | stateful wrappers around inputs                  |
-| CSS/Inline Styles      | 🛠      | ✅    | syntax for inline/conditional styles             |
-| 1st class global state | 🛠      | ✅    | redux/recoil/mobx on top of context              |
-| ----- Phase 2 -----    | -----  | ----- | -----                                            |
-| 1st class router       | 👀     | ✅    | Hook built on top of history                     |
-| Assets                 | 👀     | ✅    | include css/svg/img url statically               |
-| Integrated classnames  | 🛠      | 👀    | built-in `classnames`                            |
-| Suspense               | 👀     | 👀    | schedule future render from future/promise       |
-| Transition             | 👀     | 👀    | High-level control over suspense                 |
-| Animation              | 👀     | ✅    | Spring-style animations                          |
-| Mobile                 | 👀     | ✅    | Render with cacao                                |
-| Desktop (native)       | 👀     | ✅    | Render with native desktop                       |
-| 3D Renderer            | 👀     | ✅    | react-three-fiber                                |
-| ----- Phase 3 -----    | -----  | ----- | -----                                            |
-| Portal                 | 👀     | ✅    | cast elements through tree                       |
-| Error/Panic boundary   | 👀     | ✅    | catch panics and display custom BSOD             |
-| Code-splitting         | 👀     | ✅    | Make bundle smaller/lazy                         |
-| LiveView               | 👀     | 👀    | Example for SSR + WASM apps                      |
+Parity has moved to the homepage
 
 ## Required services:
 
 ---
 
-Gloo is covering a lot of these. We want to build hooks around these, and provide examples on how to use them.
+Gloo is covering a lot of these. We want to build hooks around these and provide examples on how to use them.
 https://github.com/rustwasm/gloo
 
-If the gloo service doesn't exist, then we need to contribute to the project
+For example, resize observer would function like this:
+
+```rust
+static Example: FC<()> = |cx| {
+    let observer = use_resize_observer();
+
+    cx.render(rsx!(
+        div { ref: observer.node_ref
+            "Size, x: {observer.x} y: {observer.y}"
+        }
+    ))
+}
+```
+
+However, resize observing is _not_ cross-platform, so this hook (internally) needs to abstract over the rendering platform.
+
+For other services, we shell out to gloo. If the gloo service doesn't exist, then we need to contribute to the project to make sure it exists.
 
 | Service                      | Hook examples | Current Projects |
 | ---------------------------- | ------------- | ---------------- |

+ 4 - 1
notes/SOLVEDPROBLEMS.md

@@ -438,6 +438,8 @@ abstract the real dom
 
 ```rust
 
+struct VirtualDom<Dom: RealDom>
+
 trait RealDom {
     type Node: RealNode;
     fn get_node(&self, id: u32) -> &Self::Node;
@@ -454,7 +456,8 @@ trait RealNode {
     fn set_attr(&mut self, name, value);
     fn set_class(&mut self);
     fn remove_attr(&mut self);
-    fn downcast_mut<T>(&mut self) -> Option<&mut T>;
+    // We can't have a generic type in trait objects, so instead we provide the inner as Any
+    fn raw_node_as_any_mut(&mut self) -> &mut dyn Any;
 }
 
 impl VirtualDom<Dom: RealDom> {

+ 3 - 1
notes/TODO.md

@@ -2,7 +2,7 @@
 - [] Transition away from names and towards compile-time safe tags
 - [] Fix diffing of fragments
 - [] Properly integrate memoization to prevent safety issues with children
-- [] Understand the issue with callbacks (outdated generations)
+- [] Understand and fix the issue with callbacks (outdated generations)
 - [] Fix examples for core, web, ssr, and general
 - [] Finish up documentation
 - [] Polish the Recoil (Dirac?) API
@@ -16,3 +16,5 @@
 - [] ...some how deserialize (hydrate) the dom state?
 - [] Implement controlled inputs for select and textarea
 - [] ...somehow... noderefs....
+
+use_state(&cx, )

+ 2 - 2
packages/cli/Cargo.toml

@@ -11,13 +11,13 @@ description = "CLI tool for developing, testing, and publishing Dioxus apps"
 [dependencies]
 thiserror = "1.0.23"
 log = "0.4.13"
-fern = { version = "0.6.0", features = ["colored"] }
+fern = { version="0.6.0", features=["colored"] }
 wasm-bindgen-cli-support = "0.2.73"
 anyhow = "1.0.38"
 argh = "0.1.4"
 serde = "1.0.120"
 serde_json = "1.0.61"
-async-std = { version = "1.9.0", features = ["attributes"] }
+async-std = { version="1.9.0", features=["attributes"] }
 tide = "0.15.0"
 fs_extra = "1.2.0"
 

+ 1 - 1
packages/cli/src/builder.rs

@@ -131,7 +131,7 @@ fn gen_page(module: &str) -> String {
     <!-- Note the usage of `type=module` here as this is an ES6 module -->
     <script type="module">
       import init from "{}";
-      init();
+      init("./wasm/module_bg.wasm");
     </script>
   </body>
 </html>

+ 6 - 3
packages/core-macro/examples/prop_test.rs

@@ -2,18 +2,21 @@ fn main() {}
 
 pub mod dioxus {
     pub mod prelude {
-        pub unsafe trait Properties {
+        pub trait Properties {
             type Builder;
-            const CAN_BE_MEMOIZED: bool;
             fn builder() -> Self::Builder;
+            unsafe fn memoize(&self, other: &Self) -> bool;
         }
     }
 }
-#[derive(dioxus_core_macro::Props)]
+
+/// This implementation should require a "PartialEq" because it memoizes (no external references)
+#[derive(PartialEq, dioxus_core_macro::Props)]
 struct SomeProps {
     a: String,
 }
 
+/// This implementation does not require a "PartialEq" because it does not memoize
 #[derive(dioxus_core_macro::Props)]
 struct SomePropsTwo<'a> {
     a: &'a str,

+ 17 - 2
packages/core-macro/src/props/mod.rs

@@ -502,6 +502,7 @@ mod field_info {
 
 mod struct_info {
     use proc_macro2::TokenStream;
+    use quote::__private::ext::RepToTokensExt;
     use quote::quote;
     use syn::parse::Error;
 
@@ -569,6 +570,13 @@ mod struct_info {
                 ref builder_name,
                 ..
             } = *self;
+
+            // we're generating stuff that goes into unsafe code here
+            // we use the heuristic: are there *any* generic parameters?
+            // If so, then they might have non-static lifetimes and we can't compare two generic things that *might borrow*
+            // Therefore, we will generate code that shortcircuits the "comparison" in memoization
+            let are_there_generics = self.generics.params.len() > 0;
+
             let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
             let all_fields_param = syn::GenericParam::Type(
                 syn::Ident::new("TypedBuilderFields", proc_macro2::Span::call_site()).into(),
@@ -650,6 +658,11 @@ Finally, call `.build()` to create the instance of `{name}`.
                     .extend(predicates.predicates.clone());
             }
 
+            let can_memoize = match are_there_generics {
+                true => quote! { false  },
+                false => quote! { self == other },
+            };
+
             Ok(quote! {
                 impl #impl_generics #name #ty_generics #where_clause {
                     #[doc = #builder_method_doc]
@@ -679,12 +692,14 @@ Finally, call `.build()` to create the instance of `{name}`.
                     }
                 }
 
-                unsafe impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
+                impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
                     type Builder = #builder_name #generics_with_empty;
-                    const CAN_BE_MEMOIZED: bool = true;
                     fn builder() -> Self::Builder {
                         #name::builder()
                     }
+                    unsafe fn memoize(&self, other: &Self) -> bool {
+                        #can_memoize
+                    }
                 }
 
             })

+ 26 - 18
packages/core-macro/src/rsx/component.rs

@@ -27,21 +27,23 @@ pub struct Component {
     name: syn::Path,
     body: Vec<ComponentField>,
     children: Vec<Node>,
+    manual_props: Option<Expr>,
 }
 
 impl Parse for Component {
-    fn parse(s: ParseStream) -> Result<Self> {
+    fn parse(stream: ParseStream) -> Result<Self> {
         // let name = s.parse::<syn::ExprPath>()?;
         // todo: look into somehow getting the crate/super/etc
 
-        let name = syn::Path::parse_mod_style(s)?;
+        let name = syn::Path::parse_mod_style(stream)?;
 
         // parse the guts
         let content: ParseBuffer;
-        syn::braced!(content in s);
+        syn::braced!(content in stream);
 
         let mut body: Vec<ComponentField> = Vec::new();
         let mut children: Vec<Node> = Vec::new();
+        let mut manual_props = None;
 
         'parsing: loop {
             // [1] Break if empty
@@ -49,15 +51,10 @@ impl Parse for Component {
                 break 'parsing;
             }
 
-            if content.peek(token::Brace) && content.peek2(Token![...]) {
-                let inner: ParseBuffer;
-                syn::braced!(inner in content);
-                if inner.peek(Token![...]) {
-                    todo!("Inline props not yet supported");
-                }
-            }
-
-            if content.peek(Ident) && content.peek2(Token![:]) {
+            if content.peek(Token![..]) {
+                content.parse::<Token![..]>()?;
+                manual_props = Some(content.parse::<Expr>()?);
+            } else if content.peek(Ident) && content.peek2(Token![:]) {
                 body.push(content.parse::<ComponentField>()?);
             } else {
                 children.push(content.parse::<Node>()?);
@@ -74,6 +71,7 @@ impl Parse for Component {
             name,
             body,
             children,
+            manual_props,
         })
     }
 }
@@ -82,8 +80,13 @@ impl ToTokens for Component {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let name = &self.name;
 
-        let mut builder = quote! {
-            fc_to_builder(#name)
+        let using_manual_override = self.manual_props.is_some();
+
+        let mut builder = {
+            match &self.manual_props {
+                Some(manual_props) => quote! { #manual_props },
+                None => quote! { fc_to_builder(#name) },
+            }
         };
 
         let mut has_key = None;
@@ -92,13 +95,18 @@ impl ToTokens for Component {
             if field.name.to_string() == "key" {
                 has_key = Some(field);
             } else {
-                builder.append_all(quote! {#field});
+                match using_manual_override {
+                    true => panic!("Currently we don't support manual props and prop fields. Choose either manual props or prop fields"),
+                    false => builder.append_all(quote! {#field}),
+                }
             }
         }
 
-        builder.append_all(quote! {
-            .build()
-        });
+        if !using_manual_override {
+            builder.append_all(quote! {
+                .build()
+            });
+        }
 
         let key_token = match has_key {
             Some(field) => {

+ 6 - 0
packages/core/.vscode/spellright.dict

@@ -1,2 +1,8 @@
 Dodrio
 VDoms
+dom
+virtualdom
+ns
+nohasher
+Preact
+vnodes

+ 11 - 5
packages/core/Cargo.toml

@@ -11,13 +11,13 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi
 
 [dependencies]
 # todo: use wast for faster load/compile
-dioxus-core-macro = { path = "../core-macro", version = "0.1.1" }
+dioxus-core-macro = { path="../core-macro", version="0.1.1" }
 
 # Backs scopes and graphs between parent and children
-generational-arena = { version = "0.2.8" }
+generational-arena = { version="0.2.8" }
 
-# Bumpalo backs the VNode creation
-bumpalo = { version = "3.6.0", features = ["collections", "boxed"] }
+# Bumpalo is used as a micro heap backing each component
+bumpalo = { version="3.6.0", features=["collections", "boxed"] }
 
 # custom error type
 thiserror = "1"
@@ -25,6 +25,9 @@ thiserror = "1"
 # faster hashmaps
 fxhash = "0.2.1"
 
+# even *faster* hashmaps for index-based types
+nohash-hasher = "0.2.0"
+
 # Used in diffing
 longest-increasing-subsequence = "0.1.0"
 
@@ -32,7 +35,10 @@ longest-increasing-subsequence = "0.1.0"
 log = "0.4"
 
 # Serialize the Edits for use in Webview/Liveview instances
-serde = { version = "1", features = ["derive"], optional = true }
+serde = { version="1", features=["derive"], optional=true }
+
+smallvec = "1.6.1"
+
 
 [features]
 default = []

+ 38 - 29
packages/core/README.md

@@ -12,6 +12,19 @@ Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus
 - Dodrio: bump allocation, double buffering, and source code for NodeBuilder
 - Percy: html! macro architecture, platform-agnostic edits
 - Yew: passion and inspiration ❤️
+- InfernoJS: approach to fragments and node diffing
+- Preact: approach for normalization and ref
+
+Dioxus-core leverages some really cool techniques and hits a very high level of parity with mature frameworks. Some unique features include:
+
+- managed lifetimes for borrowed data
+- suspended nodes (task/fiber endpoints) for asynchronous vnodes
+- custom memory allocator for vnodes and all text content
+- support for fragments w/ lazy normalization
+
+There's certainly more to the story, but these optimizations make Dioxus memory use and allocation count extremely minimal. For an average application, it is likely that zero allocations will need to be performed once the app has been mounted. Only when new components are added to the dom will allocations occur - and only en mass. The space of old VNodes is dynamically recycled as new nodes are added. Additionally, Dioxus tracks the average memory footprint of previous components to estimate how much memory allocate for future components.
+
+All in all, Dioxus treats memory as an incredibly valuable resource. Combined with the memory-efficient footprint of WASM compilation, Dioxus apps can scale to thousands of components and still stay snappy and respect your RAM usage.
 
 ## Goals
 
@@ -31,40 +44,36 @@ We have big goals for Dioxus. The final implementation must:
 - Support lazy VNodes (ie VNodes that are not actually created when the html! macro is used)
 - Support advanced diffing strategies (patience, Meyers, etc)
 
-## Design Quirks
-
-- Use of "Context" as a way of mitigating threading issues and the borrow checker. (JS relies on globals)
-- html! is lazy - needs to be used with a partner function to actually allocate the html. (Good be a good thing or a bad thing)
-
 ```rust
-let text = TextRenderer::render(html! {<div>"hello world"</div>});
-// <div>hello world</div>
-```
 
-```rust
+rsx!{ "this is a text node" }
 
-fn main() {
-    tide::new()
-        .get("blah", serve_app("../"))
-        .get("blah", ws_handler(serve_app))
+rsx!{
+    div {}
+    "asd"
+    div {}
+    div {}
+}
+rsx!{
+    div {
+        a {}
+        b {}
+        c {}
+        Container {
+            Container {
+                Container {
+                    Container {
+                        Container {
+                            div {}
+                        }
+                    }
+                }
+            }
+        }
+    }
 }
 
 
-fn serve_app(ctx: &Context<()>) -> VNode {
-    let livecontext = LiveContext::new()
-        .with_handler("graph", graph_component)
-        .with_handler("graph", graph_component)
-        .with_handler("graph", graph_component)
-        .with_handler("graph", graph_component)
-        .with_handler("graph", graph_component)
-        .with_handler("graph", graph_component)
-        .build();
-
-    ctx.render(html! {
-        <LiveContext ctx={livecontext}>
-            <App />
-        </ LiveContext>
-    })
-}
+
 
 ```

+ 41 - 7
packages/core/architecture.md

@@ -1,23 +1,57 @@
 # This module includes all life-cycle related mechanics, including the virtual DOM, scopes, properties, and lifecycles.
+
 ---
+
 The VirtualDom is designed as so:
 
 VDOM contains:
+
 - An arena of component scopes.
-    - A scope contains
-        - lifecycle data
-        - hook data
+  - A scope contains
+    - lifecycle data
+    - hook data
 - Event queue
-    - An event
+  - An event
 
 A VDOM is
+
 - constructed from anything that implements "component"
 
 A "Component" is anything (normally functions) that can be ran with a context to produce VNodes
+
 - Must implement properties-builder trait which produces a properties builder
 
 A Context
+
 - Is a consumable struct
-    - Made of references to properties
-    - Holds a reference (lockable) to the underlying scope
-    - Is partially thread-safe
+  - Made of references to properties
+  - Holds a reference (lockable) to the underlying scope
+  - Is partially thread-safe
+
+# How to interact with the real dom?
+
+## idea: use only u32
+
+pros:
+
+- allows for 4,294,967,295 nodes (enough)
+- u32 is relatively small
+- doesn't add type noise
+- allows virtualdom to stay completely generic
+
+cons:
+
+- cost of querying individual nodes (about 7ns per node query for all sizes w/ nohasher)
+- old IDs need to be manually freed when subtrees are destroyed
+  - can be collected as garbage after every render
+- loss of ids between renders........................
+  - each new render doesn't know which node the old one was connected to unless it is visited
+  - When are nodes _not_ visited during diffing?
+    - They are predetermined to be removed (a parent was probed)
+    - something with keys?
+    - I think all nodes must be visited between diffs
+  -
+
+## idea: leak raw nodes and then reclaim them on drop
+
+## idea: bind

+ 4 - 2
packages/core/examples/borrowed.rs

@@ -63,10 +63,12 @@ impl PartialEq for ChildProps {
         false
     }
 }
-unsafe impl Properties for ChildProps {
+impl Properties for ChildProps {
     type Builder = ();
-    const CAN_BE_MEMOIZED: bool = false;
     fn builder() -> Self::Builder {
         ()
     }
+    unsafe fn memoize(&self, other: &Self) -> bool {
+        self == other
+    }
 }

+ 18 - 9
packages/core/src/component.rs

@@ -7,21 +7,27 @@
 
 use crate::innerlude::FC;
 
-pub unsafe trait Properties: PartialEq + Sized {
+pub trait Properties: Sized {
     type Builder;
-    const CAN_BE_MEMOIZED: bool;
     fn builder() -> Self::Builder;
+
+    /// Memoization can only happen if the props are 'static
+    /// The user must know if their props are static, but if they make a mistake, UB happens
+    /// Therefore it's unsafe to memeoize.
+    unsafe fn memoize(&self, other: &Self) -> bool;
 }
 
-unsafe impl Properties for () {
-    const CAN_BE_MEMOIZED: bool = true;
+impl Properties for () {
     type Builder = EmptyBuilder;
-
     fn builder() -> Self::Builder {
         EmptyBuilder {}
     }
+    unsafe fn memoize(&self, _other: &Self) -> bool {
+        true
+    }
 }
-
+// We allow components to use the () generic parameter if they have no props. This impl enables the "build" method
+// that the macros use to anonymously complete prop construction.
 pub struct EmptyBuilder;
 impl EmptyBuilder {
     #[inline]
@@ -30,6 +36,8 @@ impl EmptyBuilder {
     }
 }
 
+/// 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<T: Properties>(_: FC<T>) -> T::Builder {
     T::builder()
 }
@@ -39,9 +47,10 @@ pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
 ///
 /// Fragments capture a series of children without rendering extra nodes.
 ///
-///
-///
-pub static Fragment: FC<()> = |ctx| {
+/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
+/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
+#[allow(non_upper_case_globals)]
+pub const Fragment: FC<()> = |ctx| {
     use crate::prelude::*;
     ctx.render(LazyNodes::new(move |c| {
         crate::nodebuilder::vfragment(c, None, ctx.children())

+ 25 - 6
packages/core/src/debug_renderer.rs

@@ -3,6 +3,7 @@
 //!
 //! Renderers don't actually need to own the virtual dom (it's up to the implementer).
 
+use crate::innerlude::RealDom;
 use crate::{events::EventTrigger, virtual_dom::VirtualDom};
 use crate::{innerlude::Result, prelude::*};
 
@@ -15,7 +16,7 @@ impl DebugRenderer {
     ///
     /// This means that the root component must either consumes its own context, or statics are used to generate the page.
     /// The root component can access things like routing in its context.
-    pub fn new(root: impl for<'a> Fn(Context<'a, ()>) -> VNode + 'static) -> Self {
+    pub fn new(root: FC<()>) -> Self {
         Self::new_with_props(root, ())
     }
 
@@ -23,10 +24,7 @@ impl DebugRenderer {
     /// Automatically progresses the creation of the VNode tree to completion.
     ///
     /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
-    pub fn new_with_props<T: Properties + 'static>(
-        root: impl for<'a> Fn(Context<'a, T>) -> VNode + 'static,
-        root_props: T,
-    ) -> Self {
+    pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
         Self::from_vdom(VirtualDom::new_with_props(root, root_props))
     }
 
@@ -40,7 +38,7 @@ impl DebugRenderer {
         Ok(())
     }
 
-    pub fn step(&mut self, machine: &mut DiffMachine) -> Result<()> {
+    pub fn step<Dom: RealDom>(&mut self, machine: &mut DiffMachine<Dom>) -> Result<()> {
         Ok(())
     }
 
@@ -70,6 +68,27 @@ impl DebugRenderer {
     pub fn trigger_listener(&mut self, id: usize) -> Result<()> {
         Ok(())
     }
+
+    pub fn render_nodes<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()>
+    where
+        F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
+    {
+        Ok(())
+    }
+}
+
+pub struct DebugVNodeSource {
+    bump: Bump,
+}
+impl DebugVNodeSource {
+    fn new() -> Self {
+        Self { bump: Bump::new() }
+    }
+
+    fn render_nodes(&self) -> VNode {
+        // let ctx = NodeCtx
+        todo!()
+    }
 }
 
 #[cfg(test)]

+ 601 - 489
packages/core/src/diff.rs

@@ -1,4 +1,5 @@
 //! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children.
+//! The DiffMachine calculates the diffs between the old and new frames, updates the new nodes, and modifies the real dom.
 //!
 //! Notice:
 //! ------
@@ -9,15 +10,10 @@
 //! Implementation Details:
 //! -----------------------
 //!
-//! Diff the `old` node with the `new` node. Emits instructions to modify a
-//! physical DOM node that reflects `old` into something that reflects `new`.
+//! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
+//! We don't necessarily intend for changes to happen exactly during the diffing process, so the implementor may choose
+//! to batch nodes if it is more performant for their application. The u32 should be a no-op to hash,
 //!
-//! Upon entry to this function, the physical DOM node must be on the top of the
-//! change list stack:
-//!
-//!     [... node]
-//!
-//! The change list stack is in the same state when this function exits.
 //!
 //! Further Reading and Thoughts
 //! ----------------------------
@@ -30,11 +26,59 @@ use crate::{arena::ScopeArena, innerlude::*};
 use fxhash::{FxHashMap, FxHashSet};
 
 use std::{
+    any::Any,
+    cell::Cell,
     cmp::Ordering,
     rc::{Rc, Weak},
-    sync::atomic::AtomicU32,
 };
 
+/// The accompanying "real dom" exposes an imperative API for controlling the UI layout
+///
+/// Instead of having handles directly over nodes, Dioxus uses simple u32s as node IDs.
+/// This allows layouts with up to 4,294,967,295 nodes. If we use nohasher, then retrieving is very fast.
+
+/// The "RealDom" abstracts over the... real dom. Elements are mapped by ID. The RealDom is inteded to maintain a stack
+/// of real nodes as the diffing algorithm descenes through the tree. This means that whatever is on top of the stack
+/// will receive modifications. However, instead of using child-based methods for descending through the tree, we instead
+/// ask the RealDom to either push or pop real nodes onto the stack. This saves us the indexing cost while working on a
+/// single node
+pub trait RealDom {
+    // Navigation
+    fn push_root(&mut self, root: RealDomNode);
+
+    // Add Nodes to the dom
+    fn append_child(&mut self);
+    fn replace_with(&mut self);
+
+    // Remove Nodesfrom the dom
+    fn remove(&mut self);
+    fn remove_all_children(&mut self);
+
+    // Create
+    fn create_text_node(&mut self, text: &str) -> RealDomNode;
+    fn create_element(&mut self, tag: &str) -> RealDomNode;
+    fn create_element_ns(&mut self, tag: &str, namespace: &str) -> RealDomNode;
+
+    // events
+    fn new_event_listener(
+        &mut self,
+        event: &str,
+        scope: ScopeIdx,
+        element_id: usize,
+        realnode: RealDomNode,
+    );
+    // fn new_event_listener(&mut self, event: &str);
+    fn remove_event_listener(&mut self, event: &str);
+
+    // modify
+    fn set_text(&mut self, text: &str);
+    fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool);
+    fn remove_attribute(&mut self, name: &str);
+
+    // node ref
+    fn raw_node_as_any_mut(&self) -> &mut dyn Any;
+}
+
 /// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while
 /// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event.
 ///
@@ -47,169 +91,203 @@ use std::{
 /// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components
 /// that were modified by the eventtrigger. This prevents doubly evaluating components if they were both updated via
 /// subscriptions and props changes.
-pub struct DiffMachine<'a> {
-    pub create_diffs: bool,
+pub struct DiffMachine<'a, Dom: RealDom> {
+    pub dom: &'a mut Dom,
     pub cur_idx: ScopeIdx,
-    pub change_list: EditMachine<'a>,
     pub diffed: FxHashSet<ScopeIdx>,
     pub components: ScopeArena,
     pub event_queue: EventQueue,
     pub seen_nodes: FxHashSet<ScopeIdx>,
 }
 
-static COUNTER: AtomicU32 = AtomicU32::new(1);
-fn next_id() -> u32 {
-    COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
-}
-
-impl<'a> DiffMachine<'a> {
-    pub fn new(components: ScopeArena, cur_idx: ScopeIdx, event_queue: EventQueue) -> Self {
+impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
+    pub fn new(
+        dom: &'a mut Dom,
+        components: ScopeArena,
+        cur_idx: ScopeIdx,
+        event_queue: EventQueue,
+    ) -> Self {
         Self {
             components,
+            dom,
             cur_idx,
             event_queue,
-            create_diffs: true,
-            change_list: EditMachine::new(),
             diffed: FxHashSet::default(),
             seen_nodes: FxHashSet::default(),
         }
     }
-
-    pub fn consume(self) -> EditList<'a> {
-        self.change_list.emitter
-    }
-
+    // Diff the `old` node with the `new` node. Emits instructions to modify a
+    // physical DOM node that reflects `old` into something that reflects `new`.
+    //
+    // Upon entry to this function, the physical DOM node must be on the top of the
+    // change list stack:
+    //
+    //     [... node]
+    //
+    // The change list stack is in the same state when this function exits.
+    // In the case of Fragments, the parent node is on the stack
     pub fn diff_node(&mut self, old_node: &VNode<'a>, new_node: &VNode<'a>) {
-        // pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
+        // pub fn diff_node(&self, old: &VNode<'a>, new: &VNode<'a>) {
         /*
         For each valid case, we "commit traversal", meaning we save this current position in the tree.
         Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
         When re-entering, we reuse the EditList in DiffState
         */
-        match (old_node, new_node) {
-            (VNode::Text(old_text), VNode::Text(new_text)) => {
-                if old_text != new_text {
-                    self.change_list.commit_traversal();
-                    self.change_list.set_text(new_text);
-                }
-            }
-
-            (VNode::Text(_), VNode::Element(_)) => {
-                self.change_list.commit_traversal();
-                self.create(new_node);
-                self.change_list.replace_with();
-            }
-
-            (VNode::Element(_), VNode::Text(_)) => {
-                self.change_list.commit_traversal();
-                self.create(new_node);
-                self.change_list.replace_with();
-            }
+        match old_node {
+            VNode::Element(old) => match new_node {
+                // New node is an element, old node was en element, need to investiage more deeply
+                VNode::Element(new) => {
+                    // If the element type is completely different, the element needs to be re-rendered completely
+                    // This is an optimization React makes due to how users structure their code
+                    if new.tag_name != old.tag_name || new.namespace != old.namespace {
+                        self.create(new_node);
+                        self.dom.replace_with();
+                        return;
+                    }
+                    new.dom_id.set(old.dom_id.get());
 
-            (VNode::Element(eold), VNode::Element(enew)) => {
-                // If the element type is completely different, the element needs to be re-rendered completely
-                if enew.tag_name != eold.tag_name || enew.namespace != eold.namespace {
-                    self.change_list.commit_traversal();
-                    self.change_list.replace_with();
-                    return;
+                    self.diff_listeners(old.listeners, new.listeners);
+                    self.diff_attr(old.attributes, new.attributes, new.namespace.is_some());
+                    self.diff_children(old.children, new.children);
+                }
+                // New node is a text element, need to replace the element with a simple text node
+                VNode::Text(_) => {
+                    log::debug!("Replacing el with text");
+                    self.create(new_node);
+                    self.dom.replace_with();
                 }
 
-                self.diff_listeners(eold.listeners, enew.listeners);
-                self.diff_attr(eold.attributes, enew.attributes, enew.namespace.is_some());
-                self.diff_children(eold.children, enew.children);
-            }
+                // New node is a component
+                // Make the component and replace our element on the stack with it
+                VNode::Component(_) => {
+                    self.create(new_node);
+                    self.dom.replace_with();
+                }
 
-            (VNode::Component(cold), VNode::Component(cnew)) => {
-                // Make sure we're dealing with the same component (by function pointer)
-                if cold.user_fc == cnew.user_fc {
-                    // Make sure the new component vnode is referencing the right scope id
-                    let scope_id = cold.ass_scope.borrow().clone();
-                    *cnew.ass_scope.borrow_mut() = scope_id;
-
-                    // make sure the component's caller function is up to date
-                    self.components
-                        .with_scope(scope_id.unwrap(), |scope| {
-                            scope.caller = Rc::downgrade(&cnew.caller)
-                        })
-                        .unwrap();
-
-                    // React doesn't automatically memoize, but we do.
-                    // The cost is low enough to make it worth checking
-                    let should_render = match cold.comparator {
-                        Some(comparator) => comparator(cnew),
-                        None => true,
-                    };
-
-                    if should_render {
-                        self.change_list.commit_traversal();
-                        self.components
-                            .with_scope(scope_id.unwrap(), |f| {
-                                f.run_scope().unwrap();
-                            })
-                            .unwrap();
-                        // diff_machine.change_list.load_known_root(root_id);
-                        // run the scope
-                        //
-                    } else {
-                        // Component has memoized itself and doesn't need to be re-rendered.
-                        // We still need to make sure the child's props are up-to-date.
-                        // Don't commit traversal
+                // New node is actually a sequence of nodes.
+                // We need to replace this one node with a sequence of nodes
+                // Not yet implement because it's kinda hairy
+                VNode::Fragment(_) => todo!(),
+
+                // New Node is actually suspended. Todo
+                VNode::Suspended => todo!(),
+            },
+
+            // Old element was text
+            VNode::Text(old) => match new_node {
+                VNode::Text(new) => {
+                    if old.text != new.text {
+                        log::debug!("Text has changed {}, {}", old.text, new.text);
+                        self.dom.set_text(new.text);
                     }
-                } else {
-                    // A new component has shown up! We need to destroy the old node
-
-                    // Wipe the old one and plant the new one
-                    self.change_list.commit_traversal();
+                    new.dom_id.set(old.dom_id.get());
+                }
+                VNode::Element(_) | VNode::Component(_) => {
                     self.create(new_node);
-                    self.change_list.replace_with();
-
-                    // Now we need to remove the old scope and all of its descendents
-                    let old_scope = cold.ass_scope.borrow().as_ref().unwrap().clone();
-                    self.destroy_scopes(old_scope);
+                    self.dom.replace_with();
                 }
-            }
 
-            // todo: knock out any listeners
-            (_, VNode::Component(_)) => {
-                self.change_list.commit_traversal();
-                self.create(new_node);
-                self.change_list.replace_with();
-            }
-
-            // A component is being torn down in favor of a non-component node
-            (VNode::Component(_old), _) => {
-                self.change_list.commit_traversal();
-                self.create(new_node);
-                self.change_list.replace_with();
-
-                // Destroy the original scope and any of its children
-                self.destroy_scopes(_old.ass_scope.borrow().unwrap());
-            }
+                // TODO on handling these types
+                VNode::Fragment(_) => todo!(),
+                VNode::Suspended => todo!(),
+            },
+
+            // Old element was a component
+            VNode::Component(old) => {
+                match new_node {
+                    // It's something entirely different
+                    VNode::Element(_) | VNode::Text(_) => {
+                        self.create(new_node);
+                        self.dom.replace_with();
+                    }
 
-            // Anything suspended is not enabled ATM
-            (VNode::Suspended, _) | (_, VNode::Suspended) => {
-                todo!("Suspended components not currently available")
+                    // It's also a component
+                    VNode::Component(new) => {
+                        match old.user_fc == new.user_fc {
+                            // Make sure we're dealing with the same component (by function pointer)
+                            true => {
+                                // Make sure the new component vnode is referencing the right scope id
+                                let scope_id = old.ass_scope.borrow().clone();
+                                *new.ass_scope.borrow_mut() = scope_id;
+
+                                // make sure the component's caller function is up to date
+                                self.components
+                                    .with_scope(scope_id.unwrap(), |scope| {
+                                        scope.caller = Rc::downgrade(&new.caller)
+                                    })
+                                    .unwrap();
+
+                                // React doesn't automatically memoize, but we do.
+                                // The cost is low enough to make it worth checking
+                                let should_render = match old.comparator {
+                                    Some(comparator) => comparator(new),
+                                    None => true,
+                                };
+
+                                if should_render {
+                                    // // self.dom.commit_traversal();
+                                    self.components
+                                        .with_scope(scope_id.unwrap(), |f| {
+                                            f.run_scope().unwrap();
+                                        })
+                                        .unwrap();
+                                    // diff_machine.change_list.load_known_root(root_id);
+                                    // run the scope
+                                    //
+                                } else {
+                                    // Component has memoized itself and doesn't need to be re-rendered.
+                                    // We still need to make sure the child's props are up-to-date.
+                                    // Don't commit traversal
+                                }
+                            }
+                            // It's an entirely different component
+                            false => {
+                                // A new component has shown up! We need to destroy the old node
+
+                                // Wipe the old one and plant the new one
+                                // self.dom.commit_traversal();
+                                // self.dom.replace_node_with(old.dom_id, new.dom_id);
+                                // self.create(new_node);
+                                // self.dom.replace_with();
+                                self.create(new_node);
+                                // self.create_and_repalce(new_node, old.mounted_root.get());
+
+                                // Now we need to remove the old scope and all of its descendents
+                                let old_scope = old.ass_scope.borrow().as_ref().unwrap().clone();
+                                self.destroy_scopes(old_scope);
+                            }
+                        }
+                    }
+                    VNode::Fragment(_) => todo!(),
+                    VNode::Suspended => todo!(),
+                }
             }
 
-            // Fragments are special
-            // we actually have to remove a bunch of nodes
-            (VNode::Fragment(_), _) => {
-                todo!("Fragments not currently supported in diffing")
-            }
+            VNode::Fragment(old) => {
+                //
+                match new_node {
+                    VNode::Fragment(_) => todo!(),
 
-            (VNode::Fragment(_), VNode::Fragment(_)) => {
-                todo!("Fragments not currently supported in diffing")
+                    // going from fragment to element means we're going from many (or potentially none) to one
+                    VNode::Element(new) => {}
+                    VNode::Text(_) => todo!(),
+                    VNode::Suspended => todo!(),
+                    VNode::Component(_) => todo!(),
+                }
             }
 
-            (old_n, VNode::Fragment(_)) => {
-                match old_n {
+            // a suspended node will perform a mem-copy of the previous elements until it is ready
+            // this means that event listeners will need to be disabled and removed
+            // it also means that props will need to disabled - IE if the node "came out of hibernation" any props should be considered outdated
+            VNode::Suspended => {
+                //
+                match new_node {
+                    VNode::Suspended => todo!(),
                     VNode::Element(_) => todo!(),
                     VNode::Text(_) => todo!(),
                     VNode::Fragment(_) => todo!(),
-                    VNode::Suspended => todo!(),
                     VNode::Component(_) => todo!(),
                 }
-                todo!("Fragments not currently supported in diffing")
             }
         }
     }
@@ -224,33 +302,38 @@ impl<'a> DiffMachine<'a> {
     //
     //     [... node]
     fn create(&mut self, node: &VNode<'a>) {
-        debug_assert!(self.change_list.traversal_is_committed());
+        // debug_assert!(self.dom.traversal_is_committed());
         match node {
             VNode::Text(text) => {
-                self.change_list.create_text_node(text);
+                let real_id = self.dom.create_text_node(text.text);
+                text.dom_id.set(real_id);
             }
-            VNode::Element(&VElement {
-                key,
-                tag_name,
-                listeners,
-                attributes,
-                children,
-                namespace,
-            }) => {
+            VNode::Element(el) => {
+                let VElement {
+                    key,
+                    tag_name,
+                    listeners,
+                    attributes,
+                    children,
+                    namespace,
+                    dom_id,
+                } = el;
                 // log::info!("Creating {:#?}", node);
-                if let Some(namespace) = namespace {
-                    self.change_list.create_element_ns(tag_name, namespace);
+                let real_id = if let Some(namespace) = namespace {
+                    self.dom.create_element_ns(tag_name, namespace)
                 } else {
-                    self.change_list.create_element(tag_name);
-                }
-
-                listeners.iter().enumerate().for_each(|(_id, listener)| {
-                    self.change_list
-                        .new_event_listener(listener.event, listener.scope, listener.id)
+                    self.dom.create_element(tag_name)
+                };
+                el.dom_id.set(real_id);
+
+                listeners.iter().enumerate().for_each(|(idx, listener)| {
+                    self.dom
+                        .new_event_listener(listener.event, listener.scope, idx, real_id);
+                    listener.mounted_node.set(real_id);
                 });
 
-                for attr in attributes {
-                    self.change_list
+                for attr in *attributes {
+                    self.dom
                         .set_attribute(&attr.name, &attr.value, namespace.is_some());
                 }
 
@@ -260,30 +343,29 @@ impl<'a> DiffMachine<'a> {
                 // to emit three instructions to (1) create a text node, (2) set its
                 // text content, and finally (3) append the text node to this
                 // parent.
-                if children.len() == 1 {
-                    if let VNode::Text(text) = children[0] {
-                        self.change_list.set_text(text);
-                        return;
-                    }
-                }
-
-                for child in children {
+                // if children.len() == 1 {
+                //     if let VNode::Text(text) = &children[0] {
+                //         self.dom.set_text(text.text);
+                //         return;
+                //     }
+                // }
+
+                for child in *children {
                     self.create(child);
                     if let VNode::Fragment(_) = child {
                         // do nothing
                         // fragments append themselves
                     } else {
-                        self.change_list.append_child();
+                        self.dom.append_child();
                     }
                 }
             }
 
             VNode::Component(component) => {
-                self.change_list
-                    .create_text_node("placeholder for vcomponent");
+                self.dom.create_text_node("placeholder for vcomponent");
 
-                let root_id = next_id();
-                self.change_list.save_known_root(root_id);
+                // let root_id = next_id();
+                // self.dom.save_known_root(root_id);
 
                 log::debug!("Mounting a new component");
                 let caller: Weak<OpaqueComponent> = Rc::downgrade(&component.caller);
@@ -333,6 +415,7 @@ impl<'a> DiffMachine<'a> {
                 new_component.run_scope().unwrap();
 
                 // And then run the diff algorithm
+                // todo!();
                 self.diff_node(new_component.old_frame(), new_component.next_frame());
 
                 // Finally, insert this node as a seen node.
@@ -343,8 +426,9 @@ impl<'a> DiffMachine<'a> {
             VNode::Fragment(frag) => {
                 // create the children directly in the space
                 for child in frag.children {
-                    self.create(child);
-                    self.change_list.append_child();
+                    todo!()
+                    // self.create(child);
+                    // self.dom.append_child();
                 }
             }
 
@@ -353,7 +437,9 @@ impl<'a> DiffMachine<'a> {
             }
         }
     }
+}
 
+impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
     /// Destroy a scope and all of its descendents.
     ///
     /// Calling this will run the destuctors on all hooks in the tree.
@@ -395,8 +481,10 @@ impl<'a> DiffMachine<'a> {
     // The change list stack is left unchanged.
     fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) {
         if !old.is_empty() || !new.is_empty() {
-            self.change_list.commit_traversal();
+            // self.dom.commit_traversal();
         }
+        // TODO
+        // what does "diffing listeners" even mean?
 
         'outer1: for (_l_idx, new_l) in new.iter().enumerate() {
             // go through each new listener
@@ -404,33 +492,34 @@ impl<'a> DiffMachine<'a> {
             // if any characteristics changed, remove and then re-add
 
             // if nothing changed, then just move on
-
             let event_type = new_l.event;
 
             for old_l in old {
                 if new_l.event == old_l.event {
-                    if new_l.id != old_l.id {
-                        self.change_list.remove_event_listener(event_type);
-                        self.change_list
-                            .update_event_listener(event_type, new_l.scope, new_l.id)
-                    }
+                    new_l.mounted_node.set(old_l.mounted_node.get());
+                    // if new_l.id != old_l.id {
+                    //     self.dom.remove_event_listener(event_type);
+                    //     // TODO! we need to mess with events and assign them by RealDomNode
+                    //     // self.dom
+                    //     //     .update_event_listener(event_type, new_l.scope, new_l.id)
+                    // }
 
                     continue 'outer1;
                 }
             }
 
-            self.change_list
-                .new_event_listener(event_type, new_l.scope, new_l.id);
+            // self.dom
+            //     .new_event_listener(event_type, new_l.scope, new_l.id);
         }
 
-        'outer2: for old_l in old {
-            for new_l in new {
-                if new_l.event == old_l.event {
-                    continue 'outer2;
-                }
-            }
-            self.change_list.remove_event_listener(old_l.event);
-        }
+        // 'outer2: for old_l in old {
+        //     for new_l in new {
+        //         if new_l.event == old_l.event {
+        //             continue 'outer2;
+        //         }
+        //     }
+        //     self.dom.remove_event_listener(old_l.event);
+        // }
     }
 
     // Diff a node's attributes.
@@ -453,19 +542,16 @@ impl<'a> DiffMachine<'a> {
         // With the Rsx and Html macros, this will almost always be the case
         'outer: for new_attr in new {
             if new_attr.is_volatile() {
-                self.change_list.commit_traversal();
-                self.change_list
+                // self.dom.commit_traversal();
+                self.dom
                     .set_attribute(new_attr.name, new_attr.value, is_namespaced);
             } else {
                 for old_attr in old {
                     if old_attr.name == new_attr.name {
                         if old_attr.value != new_attr.value {
-                            self.change_list.commit_traversal();
-                            self.change_list.set_attribute(
-                                new_attr.name,
-                                new_attr.value,
-                                is_namespaced,
-                            );
+                            // self.dom.commit_traversal();
+                            self.dom
+                                .set_attribute(new_attr.name, new_attr.value, is_namespaced);
                         }
                         continue 'outer;
                     } else {
@@ -473,8 +559,8 @@ impl<'a> DiffMachine<'a> {
                     }
                 }
 
-                self.change_list.commit_traversal();
-                self.change_list
+                // self.dom.commit_traversal();
+                self.dom
                     .set_attribute(new_attr.name, new_attr.value, is_namespaced);
             }
         }
@@ -486,8 +572,8 @@ impl<'a> DiffMachine<'a> {
                 }
             }
 
-            self.change_list.commit_traversal();
-            self.change_list.remove_attribute(old_attr.name);
+            // self.dom.commit_traversal();
+            self.dom.remove_attribute(old_attr.name);
         }
     }
 
@@ -502,23 +588,26 @@ impl<'a> DiffMachine<'a> {
     fn diff_children(&mut self, old: &'a [VNode<'a>], new: &'a [VNode<'a>]) {
         if new.is_empty() {
             if !old.is_empty() {
-                self.change_list.commit_traversal();
+                // self.dom.commit_traversal();
                 self.remove_all_children(old);
             }
             return;
         }
 
         if new.len() == 1 {
-            match (old.first(), &new[0]) {
-                (Some(&VNode::Text(old_text)), &VNode::Text(new_text)) if old_text == new_text => {
+            match (&old.first(), &new[0]) {
+                (Some(VNode::Text(old_vtext)), VNode::Text(new_vtext))
+                    if old_vtext.text == new_vtext.text =>
+                {
                     // Don't take this fast path...
                 }
 
-                (_, &VNode::Text(text)) => {
-                    self.change_list.commit_traversal();
-                    self.change_list.set_text(text);
-                    return;
-                }
+                // (_, VNode::Text(text)) => {
+                //     // self.dom.commit_traversal();
+                //     log::debug!("using optimized text set");
+                //     self.dom.set_text(text.text);
+                //     return;
+                // }
 
                 // todo: any more optimizations
                 (_, _) => {}
@@ -527,7 +616,7 @@ impl<'a> DiffMachine<'a> {
 
         if old.is_empty() {
             if !new.is_empty() {
-                self.change_list.commit_traversal();
+                // self.dom.commit_traversal();
                 self.create_and_append_children(new);
             }
             return;
@@ -546,9 +635,10 @@ impl<'a> DiffMachine<'a> {
         );
 
         if new_is_keyed && old_is_keyed {
-            let t = self.change_list.next_temporary();
-            self.diff_keyed_children(old, new);
-            self.change_list.set_next_temporary(t);
+            todo!("Not yet implemented a migration away from temporaries");
+            // let t = self.dom.next_temporary();
+            // self.diff_keyed_children(old, new);
+            // self.dom.set_next_temporary(t);
         } else {
             self.diff_non_keyed_children(old, new);
         }
@@ -575,7 +665,8 @@ impl<'a> DiffMachine<'a> {
     //     [... parent]
     //
     // Upon exiting, the change list stack is in the same state.
-    fn diff_keyed_children(&mut self, old: &[VNode<'a>], new: &[VNode<'a>]) {
+    fn diff_keyed_children(&self, old: &[VNode<'a>], new: &[VNode<'a>]) {
+        todo!();
         // if cfg!(debug_assertions) {
         //     let mut keys = fxhash::FxHashSet::default();
         //     let mut assert_unique_keys = |children: &[VNode]| {
@@ -658,42 +749,43 @@ impl<'a> DiffMachine<'a> {
     //     [... parent]
     //
     // Upon exit, the change list stack is the same.
-    fn diff_keyed_prefix(&mut self, old: &[VNode<'a>], new: &[VNode<'a>]) -> KeyedPrefixResult {
-        self.change_list.go_down();
-        let mut shared_prefix_count = 0;
+    fn diff_keyed_prefix(&self, old: &[VNode<'a>], new: &[VNode<'a>]) -> KeyedPrefixResult {
+        todo!()
+        // self.dom.go_down();
+        // let mut shared_prefix_count = 0;
 
-        for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
-            if old.key() != new.key() {
-                break;
-            }
+        // for (i, (old, new)) in old.iter().zip(new.iter()).enumerate() {
+        //     if old.key() != new.key() {
+        //         break;
+        //     }
 
-            self.change_list.go_to_sibling(i);
+        //     self.dom.go_to_sibling(i);
 
-            self.diff_node(old, new);
+        //     self.diff_node(old, new);
 
-            shared_prefix_count += 1;
-        }
+        //     shared_prefix_count += 1;
+        // }
 
-        // If that was all of the old children, then create and append the remaining
-        // new children and we're finished.
-        if shared_prefix_count == old.len() {
-            self.change_list.go_up();
-            self.change_list.commit_traversal();
-            self.create_and_append_children(&new[shared_prefix_count..]);
-            return KeyedPrefixResult::Finished;
-        }
+        // // If that was all of the old children, then create and append the remaining
+        // // new children and we're finished.
+        // if shared_prefix_count == old.len() {
+        //     self.dom.go_up();
+        //     // self.dom.commit_traversal();
+        //     self.create_and_append_children(&new[shared_prefix_count..]);
+        //     return KeyedPrefixResult::Finished;
+        // }
 
-        // And if that was all of the new children, then remove all of the remaining
-        // old children and we're finished.
-        if shared_prefix_count == new.len() {
-            self.change_list.go_to_sibling(shared_prefix_count);
-            self.change_list.commit_traversal();
-            self.remove_self_and_next_siblings(&old[shared_prefix_count..]);
-            return KeyedPrefixResult::Finished;
-        }
+        // // And if that was all of the new children, then remove all of the remaining
+        // // old children and we're finished.
+        // if shared_prefix_count == new.len() {
+        //     self.dom.go_to_sibling(shared_prefix_count);
+        //     // self.dom.commit_traversal();
+        //     self.remove_self_and_next_siblings(&old[shared_prefix_count..]);
+        //     return KeyedPrefixResult::Finished;
+        // }
 
-        self.change_list.go_up();
-        KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
+        // self.dom.go_up();
+        // KeyedPrefixResult::MoreWorkToDo(shared_prefix_count)
     }
 
     // The most-general, expensive code path for keyed children diffing.
@@ -710,222 +802,223 @@ impl<'a> DiffMachine<'a> {
     //
     // Upon exit from this function, it will be restored to that same state.
     fn diff_keyed_middle(
-        &mut self,
+        &self,
         old: &[VNode<'a>],
         mut new: &[VNode<'a>],
         shared_prefix_count: usize,
         shared_suffix_count: usize,
         old_shared_suffix_start: usize,
     ) {
-        // Should have already diffed the shared-key prefixes and suffixes.
-        debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
-        debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
-
-        // The algorithm below relies upon using `u32::MAX` as a sentinel
-        // value, so if we have that many new nodes, it won't work. This
-        // check is a bit academic (hence only enabled in debug), since
-        // wasm32 doesn't have enough address space to hold that many nodes
-        // in memory.
-        debug_assert!(new.len() < u32::MAX as usize);
-
-        // Map from each `old` node's key to its index within `old`.
-        let mut old_key_to_old_index = FxHashMap::default();
-        old_key_to_old_index.reserve(old.len());
-        old_key_to_old_index.extend(old.iter().enumerate().map(|(i, o)| (o.key(), i)));
-
-        // The set of shared keys between `new` and `old`.
-        let mut shared_keys = FxHashSet::default();
-        // Map from each index in `new` to the index of the node in `old` that
-        // has the same key.
-        let mut new_index_to_old_index = Vec::with_capacity(new.len());
-        new_index_to_old_index.extend(new.iter().map(|n| {
-            let key = n.key();
-            if let Some(&i) = old_key_to_old_index.get(&key) {
-                shared_keys.insert(key);
-                i
-            } else {
-                u32::MAX as usize
-            }
-        }));
-
-        // If none of the old keys are reused by the new children, then we
-        // remove all the remaining old children and create the new children
-        // afresh.
-        if shared_suffix_count == 0 && shared_keys.is_empty() {
-            if shared_prefix_count == 0 {
-                self.change_list.commit_traversal();
-                self.remove_all_children(old);
-            } else {
-                self.change_list.go_down_to_child(shared_prefix_count);
-                self.change_list.commit_traversal();
-                self.remove_self_and_next_siblings(&old[shared_prefix_count..]);
-            }
-
-            self.create_and_append_children(new);
-
-            return;
-        }
-
-        // Save each of the old children whose keys are reused in the new
-        // children.
-        let mut old_index_to_temp = vec![u32::MAX; old.len()];
-        let mut start = 0;
-        loop {
-            let end = (start..old.len())
-                .find(|&i| {
-                    let key = old[i].key();
-                    !shared_keys.contains(&key)
-                })
-                .unwrap_or(old.len());
-
-            if end - start > 0 {
-                self.change_list.commit_traversal();
-                let mut t = self.change_list.save_children_to_temporaries(
-                    shared_prefix_count + start,
-                    shared_prefix_count + end,
-                );
-                for i in start..end {
-                    old_index_to_temp[i] = t;
-                    t += 1;
-                }
-            }
-
-            debug_assert!(end <= old.len());
-            if end == old.len() {
-                break;
-            } else {
-                start = end + 1;
-            }
-        }
+        todo!()
+        // // Should have already diffed the shared-key prefixes and suffixes.
+        // debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
+        // debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
+
+        // // The algorithm below relies upon using `u32::MAX` as a sentinel
+        // // value, so if we have that many new nodes, it won't work. This
+        // // check is a bit academic (hence only enabled in debug), since
+        // // wasm32 doesn't have enough address space to hold that many nodes
+        // // in memory.
+        // debug_assert!(new.len() < u32::MAX as usize);
+
+        // // Map from each `old` node's key to its index within `old`.
+        // let mut old_key_to_old_index = FxHashMap::default();
+        // old_key_to_old_index.reserve(old.len());
+        // old_key_to_old_index.extend(old.iter().enumerate().map(|(i, o)| (o.key(), i)));
+
+        // // The set of shared keys between `new` and `old`.
+        // let mut shared_keys = FxHashSet::default();
+        // // Map from each index in `new` to the index of the node in `old` that
+        // // has the same key.
+        // let mut new_index_to_old_index = Vec::with_capacity(new.len());
+        // new_index_to_old_index.extend(new.iter().map(|n| {
+        //     let key = n.key();
+        //     if let Some(&i) = old_key_to_old_index.get(&key) {
+        //         shared_keys.insert(key);
+        //         i
+        //     } else {
+        //         u32::MAX as usize
+        //     }
+        // }));
+
+        // // If none of the old keys are reused by the new children, then we
+        // // remove all the remaining old children and create the new children
+        // // afresh.
+        // if shared_suffix_count == 0 && shared_keys.is_empty() {
+        //     if shared_prefix_count == 0 {
+        //         // self.dom.commit_traversal();
+        //         self.remove_all_children(old);
+        //     } else {
+        //         self.dom.go_down_to_child(shared_prefix_count);
+        //         // self.dom.commit_traversal();
+        //         self.remove_self_and_next_siblings(&old[shared_prefix_count..]);
+        //     }
+
+        //     self.create_and_append_children(new);
+
+        //     return;
+        // }
 
-        // Remove any old children whose keys were not reused in the new
-        // children. Remove from the end first so that we don't mess up indices.
-        let mut removed_count = 0;
-        for (i, old_child) in old.iter().enumerate().rev() {
-            if !shared_keys.contains(&old_child.key()) {
-                // registry.remove_subtree(old_child);
-                // todo
-                self.change_list.commit_traversal();
-                self.change_list.remove_child(i + shared_prefix_count);
-                removed_count += 1;
-            }
-        }
+        // // Save each of the old children whose keys are reused in the new
+        // // children.
+        // let mut old_index_to_temp = vec![u32::MAX; old.len()];
+        // let mut start = 0;
+        // loop {
+        //     let end = (start..old.len())
+        //         .find(|&i| {
+        //             let key = old[i].key();
+        //             !shared_keys.contains(&key)
+        //         })
+        //         .unwrap_or(old.len());
+
+        //     if end - start > 0 {
+        //         // self.dom.commit_traversal();
+        //         let mut t = self.dom.save_children_to_temporaries(
+        //             shared_prefix_count + start,
+        //             shared_prefix_count + end,
+        //         );
+        //         for i in start..end {
+        //             old_index_to_temp[i] = t;
+        //             t += 1;
+        //         }
+        //     }
+
+        //     debug_assert!(end <= old.len());
+        //     if end == old.len() {
+        //         break;
+        //     } else {
+        //         start = end + 1;
+        //     }
+        // }
 
-        // If there aren't any more new children, then we are done!
-        if new.is_empty() {
-            return;
-        }
+        // // Remove any old children whose keys were not reused in the new
+        // // children. Remove from the end first so that we don't mess up indices.
+        // let mut removed_count = 0;
+        // for (i, old_child) in old.iter().enumerate().rev() {
+        //     if !shared_keys.contains(&old_child.key()) {
+        //         // registry.remove_subtree(old_child);
+        //         // todo
+        //         // self.dom.commit_traversal();
+        //         self.dom.remove_child(i + shared_prefix_count);
+        //         removed_count += 1;
+        //     }
+        // }
 
-        // The longest increasing subsequence within `new_index_to_old_index`. This
-        // is the longest sequence on DOM nodes in `old` that are relatively ordered
-        // correctly within `new`. We will leave these nodes in place in the DOM,
-        // and only move nodes that are not part of the LIS. This results in the
-        // maximum number of DOM nodes left in place, AKA the minimum number of DOM
-        // nodes moved.
-        let mut new_index_is_in_lis = FxHashSet::default();
-        new_index_is_in_lis.reserve(new_index_to_old_index.len());
-        let mut predecessors = vec![0; new_index_to_old_index.len()];
-        let mut starts = vec![0; new_index_to_old_index.len()];
-        longest_increasing_subsequence::lis_with(
-            &new_index_to_old_index,
-            &mut new_index_is_in_lis,
-            |a, b| a < b,
-            &mut predecessors,
-            &mut starts,
-        );
+        // // If there aren't any more new children, then we are done!
+        // if new.is_empty() {
+        //     return;
+        // }
 
-        // Now we will iterate from the end of the new children back to the
-        // beginning, diffing old children we are reusing and if they aren't in the
-        // LIS moving them to their new destination, or creating new children. Note
-        // that iterating in reverse order lets us use `Node.prototype.insertBefore`
-        // to move/insert children.
-        //
-        // But first, we ensure that we have a child on the change list stack that
-        // we can `insertBefore`. We handle this once before looping over `new`
-        // children, so that we don't have to keep checking on every loop iteration.
-        if shared_suffix_count > 0 {
-            // There is a shared suffix after these middle children. We will be
-            // inserting before that shared suffix, so add the first child of that
-            // shared suffix to the change list stack.
-            //
-            // [... parent]
-            self.change_list
-                .go_down_to_child(old_shared_suffix_start - removed_count);
-        // [... parent first_child_of_shared_suffix]
-        } else {
-            // There is no shared suffix coming after these middle children.
-            // Therefore we have to process the last child in `new` and move it to
-            // the end of the parent's children if it isn't already there.
-            let last_index = new.len() - 1;
-            // uhhhh why an unwrap?
-            let last = new.last().unwrap();
-            // let last = new.last().unwrap_throw();
-            new = &new[..new.len() - 1];
-            if shared_keys.contains(&last.key()) {
-                let old_index = new_index_to_old_index[last_index];
-                let temp = old_index_to_temp[old_index];
-                // [... parent]
-                self.change_list.go_down_to_temp_child(temp);
-                // [... parent last]
-                self.diff_node(&old[old_index], last);
-
-                if new_index_is_in_lis.contains(&last_index) {
-                    // Don't move it, since it is already where it needs to be.
-                } else {
-                    self.change_list.commit_traversal();
-                    // [... parent last]
-                    self.change_list.append_child();
-                    // [... parent]
-                    self.change_list.go_down_to_temp_child(temp);
-                    // [... parent last]
-                }
-            } else {
-                self.change_list.commit_traversal();
-                // [... parent]
-                self.create(last);
-
-                // [... parent last]
-                self.change_list.append_child();
-                // [... parent]
-                self.change_list.go_down_to_reverse_child(0);
-                // [... parent last]
-            }
-        }
+        // // The longest increasing subsequence within `new_index_to_old_index`. This
+        // // is the longest sequence on DOM nodes in `old` that are relatively ordered
+        // // correctly within `new`. We will leave these nodes in place in the DOM,
+        // // and only move nodes that are not part of the LIS. This results in the
+        // // maximum number of DOM nodes left in place, AKA the minimum number of DOM
+        // // nodes moved.
+        // let mut new_index_is_in_lis = FxHashSet::default();
+        // new_index_is_in_lis.reserve(new_index_to_old_index.len());
+        // let mut predecessors = vec![0; new_index_to_old_index.len()];
+        // let mut starts = vec![0; new_index_to_old_index.len()];
+        // longest_increasing_subsequence::lis_with(
+        //     &new_index_to_old_index,
+        //     &mut new_index_is_in_lis,
+        //     |a, b| a < b,
+        //     &mut predecessors,
+        //     &mut starts,
+        // );
+
+        // // Now we will iterate from the end of the new children back to the
+        // // beginning, diffing old children we are reusing and if they aren't in the
+        // // LIS moving them to their new destination, or creating new children. Note
+        // // that iterating in reverse order lets us use `Node.prototype.insertBefore`
+        // // to move/insert children.
+        // //
+        // // But first, we ensure that we have a child on the change list stack that
+        // // we can `insertBefore`. We handle this once before looping over `new`
+        // // children, so that we don't have to keep checking on every loop iteration.
+        // if shared_suffix_count > 0 {
+        //     // There is a shared suffix after these middle children. We will be
+        //     // inserting before that shared suffix, so add the first child of that
+        //     // shared suffix to the change list stack.
+        //     //
+        //     // [... parent]
+        //     self.dom
+        //         .go_down_to_child(old_shared_suffix_start - removed_count);
+        // // [... parent first_child_of_shared_suffix]
+        // } else {
+        //     // There is no shared suffix coming after these middle children.
+        //     // Therefore we have to process the last child in `new` and move it to
+        //     // the end of the parent's children if it isn't already there.
+        //     let last_index = new.len() - 1;
+        //     // uhhhh why an unwrap?
+        //     let last = new.last().unwrap();
+        //     // let last = new.last().unwrap_throw();
+        //     new = &new[..new.len() - 1];
+        //     if shared_keys.contains(&last.key()) {
+        //         let old_index = new_index_to_old_index[last_index];
+        //         let temp = old_index_to_temp[old_index];
+        //         // [... parent]
+        //         self.dom.go_down_to_temp_child(temp);
+        //         // [... parent last]
+        //         self.diff_node(&old[old_index], last);
+
+        //         if new_index_is_in_lis.contains(&last_index) {
+        //             // Don't move it, since it is already where it needs to be.
+        //         } else {
+        //             // self.dom.commit_traversal();
+        //             // [... parent last]
+        //             self.dom.append_child();
+        //             // [... parent]
+        //             self.dom.go_down_to_temp_child(temp);
+        //             // [... parent last]
+        //         }
+        //     } else {
+        //         // self.dom.commit_traversal();
+        //         // [... parent]
+        //         self.create(last);
+
+        //         // [... parent last]
+        //         self.dom.append_child();
+        //         // [... parent]
+        //         self.dom.go_down_to_reverse_child(0);
+        //         // [... parent last]
+        //     }
+        // }
 
-        for (new_index, new_child) in new.iter().enumerate().rev() {
-            let old_index = new_index_to_old_index[new_index];
-            if old_index == u32::MAX as usize {
-                debug_assert!(!shared_keys.contains(&new_child.key()));
-                self.change_list.commit_traversal();
-                // [... parent successor]
-                self.create(new_child);
-                // [... parent successor new_child]
-                self.change_list.insert_before();
-            // [... parent new_child]
-            } else {
-                debug_assert!(shared_keys.contains(&new_child.key()));
-                let temp = old_index_to_temp[old_index];
-                debug_assert_ne!(temp, u32::MAX);
-
-                if new_index_is_in_lis.contains(&new_index) {
-                    // [... parent successor]
-                    self.change_list.go_to_temp_sibling(temp);
-                // [... parent new_child]
-                } else {
-                    self.change_list.commit_traversal();
-                    // [... parent successor]
-                    self.change_list.push_temporary(temp);
-                    // [... parent successor new_child]
-                    self.change_list.insert_before();
-                    // [... parent new_child]
-                }
+        // for (new_index, new_child) in new.iter().enumerate().rev() {
+        //     let old_index = new_index_to_old_index[new_index];
+        //     if old_index == u32::MAX as usize {
+        //         debug_assert!(!shared_keys.contains(&new_child.key()));
+        //         // self.dom.commit_traversal();
+        //         // [... parent successor]
+        //         self.create(new_child);
+        //         // [... parent successor new_child]
+        //         self.dom.insert_before();
+        //     // [... parent new_child]
+        //     } else {
+        //         debug_assert!(shared_keys.contains(&new_child.key()));
+        //         let temp = old_index_to_temp[old_index];
+        //         debug_assert_ne!(temp, u32::MAX);
+
+        //         if new_index_is_in_lis.contains(&new_index) {
+        //             // [... parent successor]
+        //             self.dom.go_to_temp_sibling(temp);
+        //         // [... parent new_child]
+        //         } else {
+        //             // self.dom.commit_traversal();
+        //             // [... parent successor]
+        //             self.dom.push_temporary(temp);
+        //             // [... parent successor new_child]
+        //             self.dom.insert_before();
+        //             // [... parent new_child]
+        //         }
 
-                self.diff_node(&old[old_index], new_child);
-            }
-        }
+        //         self.diff_node(&old[old_index], new_child);
+        //     }
+        // }
 
-        // [... parent child]
-        self.change_list.go_up();
+        // // [... parent child]
+        // self.dom.go_up();
         // [... parent]
     }
 
@@ -937,25 +1030,26 @@ impl<'a> DiffMachine<'a> {
     //
     // When this function exits, the change list stack remains the same.
     fn diff_keyed_suffix(
-        &mut self,
+        &self,
         old: &[VNode<'a>],
         new: &[VNode<'a>],
         new_shared_suffix_start: usize,
     ) {
-        debug_assert_eq!(old.len(), new.len());
-        debug_assert!(!old.is_empty());
+        todo!()
+        //     debug_assert_eq!(old.len(), new.len());
+        //     debug_assert!(!old.is_empty());
 
-        // [... parent]
-        self.change_list.go_down();
-        // [... parent new_child]
+        //     // [... parent]
+        //     self.dom.go_down();
+        //     // [... parent new_child]
 
-        for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
-            self.change_list.go_to_sibling(new_shared_suffix_start + i);
-            self.diff_node(old_child, new_child);
-        }
+        //     for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
+        //         self.dom.go_to_sibling(new_shared_suffix_start + i);
+        //         self.diff_node(old_child, new_child);
+        //     }
 
-        // [... parent]
-        self.change_list.go_up();
+        //     // [... parent]
+        //     self.dom.go_up();
     }
 
     // Diff children that are not keyed.
@@ -972,42 +1066,57 @@ impl<'a> DiffMachine<'a> {
         debug_assert!(!old.is_empty());
 
         //     [... parent]
-        self.change_list.go_down();
+        // self.dom.go_down();
+        // self.dom.push_root()
         //     [... parent child]
 
+        // todo!()
         for (i, (new_child, old_child)) in new.iter().zip(old.iter()).enumerate() {
             // [... parent prev_child]
-            self.change_list.go_to_sibling(i);
+            // self.dom.go_to_sibling(i);
             // [... parent this_child]
+            self.dom.push_root(old_child.get_mounted_id().unwrap());
             self.diff_node(old_child, new_child);
-        }
 
-        match old.len().cmp(&new.len()) {
-            // old.len > new.len -> removing some nodes
-            Ordering::Greater => {
-                // [... parent prev_child]
-                self.change_list.go_to_sibling(new.len());
-                // [... parent first_child_to_remove]
-                self.change_list.commit_traversal();
-                // support::remove_self_and_next_siblings(state, &old[new.len()..]);
-                self.remove_self_and_next_siblings(&old[new.len()..]);
-                // [... parent]
-            }
-            // old.len < new.len -> adding some nodes
-            Ordering::Less => {
-                // [... parent last_child]
-                self.change_list.go_up();
-                // [... parent]
-                self.change_list.commit_traversal();
-                self.create_and_append_children(&new[old.len()..]);
-            }
-            // old.len == new.len -> no nodes added/removed, but πerhaps changed
-            Ordering::Equal => {
-                // [... parent child]
-                self.change_list.go_up();
-                // [... parent]
+            let old_id = old_child.get_mounted_id().unwrap();
+            let new_id = new_child.get_mounted_id().unwrap();
+
+            log::debug!(
+                "pushed root. {:?}, {:?}",
+                old_child.get_mounted_id().unwrap(),
+                new_child.get_mounted_id().unwrap()
+            );
+            if old_id != new_id {
+                log::debug!("Mismatch: {:?}", new_child);
             }
         }
+
+        // match old.len().cmp(&new.len()) {
+        //     // old.len > new.len -> removing some nodes
+        //     Ordering::Greater => {
+        //         // [... parent prev_child]
+        //         self.dom.go_to_sibling(new.len());
+        //         // [... parent first_child_to_remove]
+        //         // self.dom.commit_traversal();
+        //         // support::remove_self_and_next_siblings(state, &old[new.len()..]);
+        //         self.remove_self_and_next_siblings(&old[new.len()..]);
+        //         // [... parent]
+        //     }
+        //     // old.len < new.len -> adding some nodes
+        //     Ordering::Less => {
+        //         // [... parent last_child]
+        //         self.dom.go_up();
+        //         // [... parent]
+        //         // self.dom.commit_traversal();
+        //         self.create_and_append_children(&new[old.len()..]);
+        //     }
+        //     // old.len == new.len -> no nodes added/removed, but πerhaps changed
+        //     Ordering::Equal => {
+        //         // [... parent child]
+        //         self.dom.go_up();
+        //         // [... parent]
+        //     }
+        // }
     }
 
     // ======================
@@ -1022,14 +1131,15 @@ impl<'a> DiffMachine<'a> {
     //
     // When this function returns, the change list stack is in the same state.
     pub fn remove_all_children(&mut self, old: &[VNode<'a>]) {
-        debug_assert!(self.change_list.traversal_is_committed());
+        // debug_assert!(self.dom.traversal_is_committed());
         log::debug!("REMOVING CHILDREN");
         for _child in old {
             // registry.remove_subtree(child);
         }
         // Fast way to remove all children: set the node's textContent to an empty
         // string.
-        self.change_list.set_text("");
+        todo!()
+        // self.dom.set_inner_text("");
     }
 
     // Create the given children and append them to the parent node.
@@ -1040,10 +1150,11 @@ impl<'a> DiffMachine<'a> {
     //
     // When this function returns, the change list stack is in the same state.
     pub fn create_and_append_children(&mut self, new: &[VNode<'a>]) {
-        debug_assert!(self.change_list.traversal_is_committed());
+        // debug_assert!(self.dom.traversal_is_committed());
         for child in new {
+            // self.create_and_append(node, parent)
             self.create(child);
-            self.change_list.append_child();
+            self.dom.append_child();
         }
     }
 
@@ -1056,11 +1167,11 @@ impl<'a> DiffMachine<'a> {
     // After the function returns, the child is no longer on the change list stack:
     //
     //     [... parent]
-    pub fn remove_self_and_next_siblings(&mut self, old: &[VNode<'a>]) {
-        debug_assert!(self.change_list.traversal_is_committed());
+    pub fn remove_self_and_next_siblings(&self, old: &[VNode<'a>]) {
+        // debug_assert!(self.dom.traversal_is_committed());
         for child in old {
             if let VNode::Component(vcomp) = child {
-                // self.change_list
+                // dom
                 //     .create_text_node("placeholder for vcomponent");
 
                 todo!()
@@ -1071,7 +1182,7 @@ impl<'a> DiffMachine<'a> {
                 // })
                 // let id = get_id();
                 // *component.stable_addr.as_ref().borrow_mut() = Some(id);
-                // self.change_list.save_known_root(id);
+                // self.dom.save_known_root(id);
                 // let scope = Rc::downgrade(&component.ass_scope);
                 // self.lifecycle_events.push_back(LifeCycleEvent::Mount {
                 //     caller: Rc::downgrade(&component.caller),
@@ -1082,7 +1193,8 @@ impl<'a> DiffMachine<'a> {
 
             // registry.remove_subtree(child);
         }
-        self.change_list.remove_self_and_next_siblings();
+        todo!()
+        // self.dom.remove_self_and_next_siblings();
     }
 }
 

+ 32 - 24
packages/core/src/events.rs

@@ -6,20 +6,20 @@
 
 use std::rc::Rc;
 
-use crate::innerlude::ScopeIdx;
+use crate::{innerlude::ScopeIdx, virtual_dom::RealDomNode};
 
 #[derive(Debug)]
 pub struct EventTrigger {
     pub component_id: ScopeIdx,
-    pub listener_id: usize,
+    pub real_node_id: RealDomNode,
     pub event: VirtualEvent,
 }
 
 impl EventTrigger {
-    pub fn new(event: VirtualEvent, scope: ScopeIdx, id: usize) -> Self {
+    pub fn new(event: VirtualEvent, scope: ScopeIdx, mounted_dom_id: RealDomNode) -> Self {
         Self {
             component_id: scope,
-            listener_id: id,
+            real_node_id: mounted_dom_id,
             event,
         }
     }
@@ -44,28 +44,36 @@ pub enum VirtualEvent {
     MouseEvent(Rc<dyn on::MouseEvent>),
     PointerEvent(Rc<dyn on::PointerEvent>),
 
+    // image event has conflicting method types
     // ImageEvent(event_data::ImageEvent),
     OtherEvent,
 }
 
 pub mod on {
+    //! This module defines the synthetic events that all Dioxus apps enable. No matter the platform, every dioxus renderer
+    //! will implement the same events and same behavior (bubbling, cancelation, etc).
+    //!
+    //! Synthetic events are immutable and wrapped in Arc. It is the intention for Dioxus renderers to re-use the underyling
+    //! Arc allocation through "get_mut"
+    //!
+    //!
+    //!
+
     #![allow(unused)]
     use std::{fmt::Debug, ops::Deref, rc::Rc};
 
     use crate::{
         builder::ElementBuilder,
-        innerlude::{Attribute, Listener, VNode},
-        virtual_dom::NodeCtx,
+        builder::NodeCtx,
+        innerlude::{Attribute, Listener, RealDomNode, VNode},
     };
+    use std::cell::Cell;
 
     use super::VirtualEvent;
 
     macro_rules! event_directory {
         ( $( $eventdata:ident: [ $( $name:ident )* ]; )* ) => {
             $(
-
-
-
                 $(
                     pub fn $name<'a>(
                         c: &'_ NodeCtx<'a>,
@@ -74,7 +82,7 @@ pub mod on {
                         let bump = &c.bump();
                         Listener {
                             event: stringify!($name),
-                            id: *c.listener_id.borrow(),
+                            mounted_node: bump.alloc(Cell::new(RealDomNode::empty())),
                             scope: c.scope_ref.arena_idx,
                             callback: bump.alloc(move |evt: VirtualEvent| match evt {
                                 VirtualEvent::$eventdata(event) => callback(event),
@@ -121,31 +129,31 @@ pub mod on {
         /// Returns whether or not a specific event is a bubbling event
         fn bubbles(&self) -> bool;
         /// Sets or returns whether the event should propagate up the hierarchy or not
-        fn cancelBubble(&self) -> ();
+        fn cancel_bubble(&self);
         /// Returns whether or not an event can have its default action prevented
         fn cancelable(&self) -> bool;
         /// Returns whether the event is composed or not
         fn composed(&self) -> bool;
         /// Returns the event's path
-        fn composedPath(&self) -> ();
+        fn composed_path(&self) -> String;
         /// Returns the element whose event listeners triggered the event
-        fn currentTarget(&self) -> ();
+        fn current_target(&self);
         /// Returns whether or not the preventDefault method was called for the event
-        fn defaultPrevented(&self) -> ();
+        fn default_prevented(&self) -> bool;
         /// Returns which phase of the event flow is currently being evaluated
-        fn eventPhase(&self) -> ();
+        fn event_phase(&self) -> usize;
         /// Returns whether or not an event is trusted
-        fn isTrusted(&self) -> ();
+        fn is_trusted(&self) -> bool;
         /// Cancels the event if it is cancelable, meaning that the default action that belongs to the event will
-        fn preventDefault(&self) -> ();
+        fn prevent_default(&self);
         /// Prevents other listeners of the same event from being called
-        fn stopImmediatePropagation(&self) -> ();
+        fn stop_immediate_propagation(&self);
         /// Prevents further propagation of an event during event flow
-        fn stopPropagation(&self) -> ();
+        fn stop_propagation(&self);
         /// Returns the element that triggered the event
-        fn target(&self) -> ();
+        fn target(&self);
         /// Returns the time (in milliseconds relative to the epoch) at which the event was created
-        fn timeStamp(&self) -> usize;
+        fn time_stamp(&self) -> usize;
     }
 
     pub trait ClipboardEvent: Debug {
@@ -180,8 +188,8 @@ pub mod on {
 
     pub trait MouseEvent: Debug {
         fn alt_key(&self) -> bool;
-        fn button(&self) -> i32;
-        fn buttons(&self) -> i32;
+        fn button(&self) -> i16;
+        fn buttons(&self) -> u16;
         fn client_x(&self) -> i32;
         fn client_y(&self) -> i32;
         fn ctrl_key(&self) -> bool;
@@ -191,7 +199,7 @@ pub mod on {
         fn screen_x(&self) -> i32;
         fn screen_y(&self) -> i32;
         fn shift_key(&self) -> bool;
-        fn get_modifier_state(&self, key_code: usize) -> bool;
+        fn get_modifier_state(&self, key_code: &str) -> bool;
     }
 
     pub trait PointerEvent: Debug {

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

@@ -13,8 +13,7 @@ pub mod component; // Logic for extending FC
 
 pub mod debug_renderer;
 pub mod diff;
-pub mod patch; // An "edit phase" described by transitions and edit operations // Test harness for validating that lifecycles and diffs work appropriately
-               // the diffing algorithm that builds the ChangeList
+
 pub mod error; // Error type we expose to the renderers
 pub mod events; // Manages the synthetic event API
 pub mod hooks; // Built-in hooks
@@ -30,14 +29,12 @@ pub mod builder {
 pub(crate) mod innerlude {
     pub use crate::component::*;
 
-    pub use crate::debug_renderer::*;
     pub use crate::diff::*;
     pub use crate::error::*;
     pub use crate::events::*;
     pub use crate::hooks::*;
     pub use crate::nodebuilder::*;
     pub use crate::nodes::*;
-    pub use crate::patch::*;
     pub use crate::virtual_dom::*;
 
     pub type FC<P> = fn(Context<P>) -> VNode;
@@ -59,7 +56,7 @@ pub mod prelude {
     pub use crate::nodebuilder::LazyNodes;
 
     pub use crate::nodebuilder::ChildrenList;
-    pub use crate::virtual_dom::NodeCtx;
+    pub use crate::nodebuilder::NodeCtx;
     // pub use nodes::iterables::IterableNodes;
     /// This type alias is an internal way of abstracting over the static functions that represent components.
     pub use crate::innerlude::FC;
@@ -77,6 +74,6 @@ pub mod prelude {
     pub use crate::diff::DiffMachine;
     pub use crate::virtual_dom::ScopeIdx;
 
-    pub use crate::debug_renderer::DebugRenderer;
+    // pub use crate::debug_renderer::DebugRenderer;
     pub use crate::hooks::*;
 }

+ 73 - 17
packages/core/src/nodebuilder.rs

@@ -1,13 +1,20 @@
 //! Helpers for building virtual DOM VNodes.
 
-use std::{any::Any, borrow::BorrowMut, fmt::Arguments, intrinsics::transmute, u128};
+use std::{
+    any::Any,
+    borrow::BorrowMut,
+    cell::{Cell, RefCell},
+    fmt::Arguments,
+    intrinsics::transmute,
+    u128,
+};
 
 use crate::{
     events::VirtualEvent,
     innerlude::{Properties, VComponent, FC},
     nodes::{Attribute, Listener, NodeKey, VNode},
     prelude::{VElement, VFragment},
-    virtual_dom::NodeCtx,
+    virtual_dom::{RealDomNode, Scope},
 };
 
 /// A virtual DOM element builder.
@@ -351,12 +358,11 @@ where
     /// ```
     pub fn on(self, event: &'static str, callback: impl Fn(VirtualEvent) + 'a) -> Self {
         let bump = &self.ctx.bump();
-
         let listener = Listener {
             event,
             callback: bump.alloc(callback),
-            id: *self.ctx.listener_id.borrow(),
             scope: self.ctx.scope_ref.arena_idx,
+            mounted_node: bump.alloc(Cell::new(RealDomNode::empty())),
         };
         self.add_listener(listener)
     }
@@ -365,7 +371,8 @@ where
         self.listeners.push(listener);
 
         // bump the context id forward
-        *self.ctx.listener_id.borrow_mut() += 1;
+        let id = self.ctx.listener_id.get();
+        self.ctx.listener_id.set(id + 1);
 
         // Add this listener to the context list
         // This casts the listener to a self-referential pointer
@@ -376,7 +383,7 @@ where
                 .scope_ref
                 .listeners
                 .borrow_mut()
-                .push(r.callback as *const _);
+                .push((r.mounted_node as *const _, r.callback as *const _));
         });
 
         self
@@ -492,10 +499,26 @@ where
     ///     .finish();
     /// ```    
     pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoVNode<'a>>) -> Self {
+        let len_before = self.children.len();
         for item in nodes {
             let child = item.into_vnode(&self.ctx);
             self.children.push(child);
         }
+        if cfg!(debug_assertions) {
+            if self.children.len() > len_before + 1 {
+                if self.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.
+                        "#,
+                    );
+                }
+            }
+        }
         self
     }
 }
@@ -535,17 +558,6 @@ impl<'a, F> VNodeBuilder<'a, LazyNodes<'a, F>> for LazyNodes<'a, F> where
 {
 }
 
-// Cover the cases where nodes are pre-rendered.
-// Likely used by enums.
-// ----
-//  let nodes = ctx.render(rsx!{ ... };
-//  rsx! { {nodes } }
-// impl<'a> IntoVNode<'a> for VNode {
-//     fn into_vnode(self, _ctx: &NodeCtx<'a>) -> VNode<'a> {
-//         self.root
-//     }
-// }
-
 // Wrap the the node-builder closure in a concrete type.
 // ---
 // This is a bit of a hack to implement the IntoVNode trait for closure types.
@@ -597,12 +609,14 @@ where
 
 impl<'a> IntoVNode<'a> for () {
     fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
+        todo!();
         VNode::Suspended
     }
 }
 
 impl<'a> IntoVNode<'a> for Option<()> {
     fn into_vnode(self, ctx: &NodeCtx<'a>) -> VNode<'a> {
+        todo!();
         VNode::Suspended
     }
 }
@@ -715,3 +729,45 @@ impl<'a, 'b> ChildrenList<'a, 'b> {
         self.children.into_bump_slice()
     }
 }
+
+// NodeCtx is used to build VNodes in the component's memory space.
+// This struct adds metadata to the final VNode about listeners, attributes, and children
+#[derive(Clone)]
+pub struct NodeCtx<'a> {
+    pub scope_ref: &'a Scope,
+    pub listener_id: Cell<usize>,
+    // pub listener_id: RefCell<usize>,
+}
+
+impl<'a> NodeCtx<'a> {
+    #[inline]
+    pub fn bump(&self) -> &'a bumpalo::Bump {
+        &self.scope_ref.cur_frame().bump
+    }
+
+    /// Create some text that's allocated along with the other vnodes
+    pub fn text(&self, args: Arguments) -> VNode<'a> {
+        text3(self.bump(), args)
+    }
+
+    /// Create an element builder
+    pub fn element<'b, Listeners, Attributes, Children>(
+        &'b self,
+        tag_name: &'static str,
+    ) -> ElementBuilder<
+        'a,
+        'b,
+        bumpalo::collections::Vec<'a, Listener<'a>>,
+        bumpalo::collections::Vec<'a, Attribute<'a>>,
+        bumpalo::collections::Vec<'a, VNode<'a>>,
+    > {
+        ElementBuilder::new(self, tag_name)
+    }
+}
+
+use std::fmt::Debug;
+impl Debug for NodeCtx<'_> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        Ok(())
+    }
+}

+ 267 - 77
packages/core/src/nodes.rs

@@ -6,15 +6,14 @@
 use crate::{
     events::VirtualEvent,
     innerlude::{Context, Properties, Scope, ScopeIdx, FC},
-    nodebuilder::text3,
-    virtual_dom::NodeCtx,
-    // support::NodeCtx,
+    nodebuilder::{text3, NodeCtx},
+    virtual_dom::RealDomNode,
 };
 use bumpalo::Bump;
 use std::{
     any::Any,
-    cell::RefCell,
-    fmt::{Arguments, Debug},
+    cell::{Cell, RefCell},
+    fmt::{Arguments, Debug, Formatter},
     marker::PhantomData,
     rc::Rc,
 };
@@ -29,7 +28,7 @@ pub enum VNode<'src> {
     Element(&'src VElement<'src>),
 
     /// A text node (node type `TEXT_NODE`).
-    Text(&'src str),
+    Text(VText<'src>),
 
     /// A fragment is a "virtual position" in the DOM
     /// Fragments may have children and keys
@@ -49,7 +48,7 @@ impl<'a> Clone for VNode<'a> {
     fn clone(&self) -> Self {
         match self {
             VNode::Element(element) => VNode::Element(element),
-            VNode::Text(text) => VNode::Text(text),
+            VNode::Text(old) => VNode::Text(old.clone()),
             VNode::Fragment(fragment) => VNode::Fragment(fragment),
             VNode::Component(component) => VNode::Component(component),
             VNode::Suspended => VNode::Suspended,
@@ -81,6 +80,7 @@ impl<'a> VNode<'a> {
             attributes,
             children,
             namespace,
+            dom_id: Cell::new(RealDomNode::empty()),
         });
         VNode::Element(element)
     }
@@ -88,7 +88,10 @@ impl<'a> VNode<'a> {
     /// Construct a new text node with the given text.
     #[inline]
     pub fn text(text: &'a str) -> VNode<'a> {
-        VNode::Text(text)
+        VNode::Text(VText {
+            text,
+            dom_id: Cell::new(RealDomNode::empty()),
+        })
     }
 
     pub fn text_args(bump: &'a Bump, args: Arguments) -> VNode<'a> {
@@ -98,7 +101,7 @@ impl<'a> VNode<'a> {
     #[inline]
     pub(crate) fn key(&self) -> NodeKey {
         match &self {
-            VNode::Text(_) => NodeKey::NONE,
+            VNode::Text { .. } => NodeKey::NONE,
             VNode::Element(e) => e.key,
             VNode::Fragment(frag) => frag.key,
             VNode::Component(c) => c.key,
@@ -107,6 +110,48 @@ impl<'a> VNode<'a> {
             VNode::Suspended => NodeKey::NONE,
         }
     }
+
+    fn get_child(&self, id: u32) -> Option<&'a VNode<'a>> {
+        todo!()
+    }
+
+    pub fn is_real(&self) -> bool {
+        match self {
+            VNode::Element(_) => true,
+            VNode::Text(_) => true,
+            VNode::Fragment(_) => false,
+            VNode::Suspended => false,
+            VNode::Component(_) => false,
+        }
+    }
+
+    pub fn get_mounted_id(&self) -> Option<RealDomNode> {
+        match self {
+            VNode::Element(el) => Some(el.dom_id.get()),
+            VNode::Text(te) => Some(te.dom_id.get()),
+            VNode::Fragment(_) => todo!(),
+            VNode::Suspended => todo!(),
+            VNode::Component(_) => todo!(),
+        }
+    }
+}
+
+impl Debug for VNode<'_> {
+    fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
+        match self {
+            VNode::Element(el) => write!(s, "element, {}", el.tag_name),
+            VNode::Text(t) => write!(s, "text, {}", t.text),
+            VNode::Fragment(_) => write!(s, "fragment"),
+            VNode::Suspended => write!(s, "suspended"),
+            VNode::Component(_) => write!(s, "component"),
+        }
+    }
+}
+
+#[derive(Clone)]
+pub struct VText<'src> {
+    pub text: &'src str,
+    pub dom_id: Cell<RealDomNode>,
 }
 
 // ========================================================
@@ -120,6 +165,7 @@ pub struct VElement<'a> {
     pub attributes: &'a [Attribute<'a>],
     pub children: &'a [VNode<'a>],
     pub namespace: Option<&'a str>,
+    pub dom_id: Cell<RealDomNode>,
 }
 
 /// An attribute on a DOM node, such as `id="my-thing"` or
@@ -167,11 +213,14 @@ pub struct Listener<'bump> {
     /// The type of event to listen for.
     pub(crate) event: &'static str,
 
+    /// Which scope?
+    /// This might not actually be relevant
     pub scope: ScopeIdx,
-    pub id: usize,
+
+    pub mounted_node: &'bump Cell<RealDomNode>,
 
     /// The callback to invoke when the event happens.
-    pub(crate) callback: &'bump (dyn Fn(VirtualEvent)),
+    pub(crate) callback: &'bump dyn Fn(VirtualEvent),
 }
 
 /// The key for keyed children.
@@ -224,7 +273,8 @@ pub type VCompAssociatedScope = Option<ScopeIdx>;
 pub struct VComponent<'src> {
     pub key: NodeKey<'src>,
 
-    pub stable_addr: RefCell<StableScopeAddres>,
+    pub mounted_root: Cell<RealDomNode>,
+
     pub ass_scope: RefCell<VCompAssociatedScope>,
 
     // pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
@@ -243,96 +293,76 @@ pub struct VComponent<'src> {
 }
 
 impl<'a> VComponent<'a> {
-    // use the type parameter on props creation and move it into a portable context
-    // this lets us keep scope generic *and* downcast its props when we need to:
-    // - perform comparisons when diffing (memoization)
-    // TODO: lift the requirement that props need to be static
-    // we want them to borrow references... maybe force implementing a "to_static_unsafe" trait
-
+    /// When the rsx! macro is called, it will check if the CanMemo flag is set to true (from the Props impl)
+    /// If it is set to true, then this method will be called which implements automatic memoization.
+    ///
+    /// If the CanMemo is `false`, then the macro will call the backup method which always defaults to "false"
     pub fn new<P: Properties + 'a>(
-        // bump: &'a Bump,
         ctx: &NodeCtx<'a>,
         component: FC<P>,
-        // props: bumpalo::boxed::Box<'a, P>,
         props: P,
         key: Option<&'a str>,
         children: &'a [VNode<'a>],
     ) -> Self {
-        // pub fn new<P: Properties + 'a>(component: FC<P>, props: P, key: Option<&'a str>) -> Self {
-        // let bad_props = unsafe { transmogrify(props) };
         let bump = ctx.bump();
-        let caller_ref = component as *const ();
-        let props = bump.alloc(props);
+        let user_fc = component as *const ();
 
+        let props = bump.alloc(props);
         let raw_props = props as *const P as *const ();
 
         let comparator: Option<&dyn Fn(&VComponent) -> bool> = {
-            if P::CAN_BE_MEMOIZED {
-                Some(bump.alloc(move |other: &VComponent| {
-                    // Safety:
-                    // We are guaranteed that the props will be of the same type because
-                    // there is no way to create a VComponent other than this `new` method.
-                    //
-                    // Therefore, if the render functions are identical (by address), then so will be
-                    // props type paramter (because it is the same render function). Therefore, we can be
-                    // sure
-                    if caller_ref == other.user_fc {
-                        // let g = other.raw_ctx.downcast_ref::<P>().unwrap();
-                        let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
-                        &props == &real_other
-                    } else {
-                        false
+            Some(bump.alloc(move |other: &VComponent| {
+                // Safety:
+                // ------
+                //
+                // Invariants:
+                // - Component function pointers are the same
+                // - Generic properties on the same function pointer are the same
+                // - Lifetime of P borrows from its parent
+                // - The parent scope still exists when method is called
+                // - Casting from T to *const () is portable
+                //
+                // Explanation:
+                //   We are guaranteed that the props will be of the same type because
+                //   there is no way to create a VComponent other than this `new` method.
+                //
+                //   Therefore, if the render functions are identical (by address), then so will be
+                //   props type paramter (because it is the same render function). Therefore, we can be
+                //   sure that it is safe to interperet the previous props raw pointer as the same props
+                //   type. From there, we can call the props' "memoize" method to see if we can
+                //   avoid re-rendering the component.
+                if user_fc == other.user_fc {
+                    let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
+                    let props_memoized = unsafe { props.memoize(&real_other) };
+                    match (props_memoized, children.len() == 0) {
+                        (true, true) => true,
+                        _ => false,
                     }
-                }))
-            } else {
-                None
-            }
+                } else {
+                    false
+                }
+            }))
         };
 
-        // let prref: &'a P = props.as_ref();
-
-        // let r = create_closure(component, raw_props);
-        // let caller: Rc<dyn for<'g> Fn(&'g Scope) -> VNode<'g>> = Rc::new(move |scope| {
-        //     // r(scope);
-        //     //
-        //     // let props2 = bad_props;
-        //     // props.as_ref();
-        //     // let ctx = Context {
-        //     //     props: prref,
-        //     //     scope,
-        //     // };
-        //     // let ctx: Context<'g, P> = todo!();
-        //     // todo!()
-        //     // let r = component(ctx);
-        //     todo!()
-        // });
-        let caller = create_closure(component, raw_props);
-
-        // let caller: Rc<dyn Fn(&Scope) -> VNode> = Rc::new(create_closure(component, raw_props));
-
-        let key = match key {
-            Some(key) => NodeKey::new(key),
-            None => NodeKey(None),
-        };
-
-        // raw_props: Box::new(props),
-        // comparator: Rc::new(props_comparator),
         Self {
-            key,
-            ass_scope: RefCell::new(None),
-            user_fc: caller_ref,
+            user_fc,
             comparator,
             raw_props,
             children,
-            caller,
-            stable_addr: RefCell::new(None),
+            ass_scope: RefCell::new(None),
+            key: match key {
+                Some(key) => NodeKey::new(key),
+                None => NodeKey(None),
+            },
+            caller: create_closure(component, raw_props),
+            mounted_root: Cell::new(RealDomNode::empty()),
         }
     }
 }
 
 type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
 
-fn create_closure<'a, P: Properties + 'a>(
+fn create_closure<'a, P: 'a>(
     component: FC<P>,
     raw_props: *const (),
 ) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
@@ -369,3 +399,163 @@ impl<'a> VFragment<'a> {
         Self { key, children }
     }
 }
+
+/// This method converts a list of nested real/virtual nodes into a stream of nodes that are definitely associated
+/// with the real dom. The only types of nodes that may be returned are text, elemets, and components.
+///
+/// Components *are* considered virtual, but this iterator can't necessarily handle them without the scope arena.
+///
+/// Why?
+/// ---
+/// Fragments are seen as virtual nodes but are actually a list of possibly-real nodes.
+/// JS implementations normalize their node lists when fragments are present. Here, we just create a new iterator
+/// that iterates through the recursive nesting of fragments.
+///
+/// Fragments are stupid and I wish we didn't need to support them.
+///
+/// This iterator only supports 3 levels of nested fragments
+///
+pub fn iterate_real_nodes<'a>(nodes: &'a [VNode<'a>]) -> RealNodeIterator<'a> {
+    RealNodeIterator::new(nodes)
+}
+
+pub struct RealNodeIterator<'a> {
+    nodes: &'a [VNode<'a>],
+
+    // this node is always a "real" node
+    // the index is "what sibling # is it"
+    // IE in a list of children on a fragment, the node will be a text node that's the 5th sibling
+    node_stack: Vec<(&'a VNode<'a>, u32)>,
+}
+
+impl<'a> RealNodeIterator<'a> {
+    // We immediately descend to the first real node we can find
+    fn new(nodes: &'a [VNode<'a>]) -> Self {
+        let mut node_stack = Vec::new();
+        if nodes.len() > 0 {
+            let mut cur_node = nodes.get(0).unwrap();
+            loop {
+                node_stack.push((cur_node, 0_u32));
+                if !cur_node.is_real() {
+                    cur_node = cur_node.get_child(0).unwrap();
+                } else {
+                    break;
+                }
+            }
+        }
+
+        Self { nodes, node_stack }
+    }
+
+    // // advances the cursor to the next element, panicing if we're on the 3rd level and still finding fragments
+    // fn advance_cursor(&mut self) {
+    //     let (mut cur_node, mut cur_id) = self.node_stack.last().unwrap();
+
+    //     while !cur_node.is_real() {
+    //         match cur_node {
+    //             VNode::Element(_) | VNode::Text(_) => todo!(),
+    //             VNode::Suspended => todo!(),
+    //             VNode::Component(_) => todo!(),
+    //             VNode::Fragment(frag) => {
+    //                 let p = frag.children;
+    //             }
+    //         }
+    //     }
+    // }
+
+    fn next_node(&mut self) -> bool {
+        let (mut cur_node, cur_id) = self.node_stack.last_mut().unwrap();
+
+        match cur_node {
+            VNode::Fragment(frag) => {
+                //
+                if *cur_id + 1 > frag.children.len() as u32 {
+                    self.node_stack.pop();
+                    let next = self.node_stack.last_mut();
+                    return false;
+                }
+                *cur_id += 1;
+                true
+            }
+
+            VNode::Element(_) => todo!(),
+            VNode::Text(_) => todo!(),
+            VNode::Suspended => todo!(),
+            VNode::Component(_) => todo!(),
+        }
+    }
+
+    fn get_current_node(&self) -> Option<&VNode<'a>> {
+        self.node_stack.last().map(|(node, id)| match node {
+            VNode::Element(_) => todo!(),
+            VNode::Text(_) => todo!(),
+            VNode::Fragment(_) => todo!(),
+            VNode::Suspended => todo!(),
+            VNode::Component(_) => todo!(),
+        })
+    }
+}
+
+impl<'a> Iterator for RealNodeIterator<'a> {
+    type Item = &'a VNode<'a>;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        todo!()
+        // let top_idx = self.nesting_idxs.get_mut(0).unwrap();
+        // let node = &self.nodes.get_mut(*top_idx as usize);
+
+        // if node.is_none() {
+        //     return None;
+        // }
+        // let node = node.unwrap();
+
+        // match node {
+        //     VNode::Element(_) | VNode::Text(_) => {
+        //         *top_idx += 1;
+        //         return Some(node);
+        //     }
+        //     VNode::Suspended => todo!(),
+        //     // we need access over the scope map
+        //     VNode::Component(_) => todo!(),
+
+        //     VNode::Fragment(frag) => {
+        //         let nest_idx = self.nesting_idxs.get_mut(1).unwrap();
+        //         let node = &frag.children.get_mut(*nest_idx as usize);
+        //         match node {
+        //             VNode::Element(_) | VNode::Text(_) => {
+        //                 *nest_idx += 1;
+        //                 return Some(node);
+        //             }
+        //             VNode::Fragment(_) => todo!(),
+        //             VNode::Suspended => todo!(),
+        //             VNode::Component(_) => todo!(),
+        //         }
+        //     }
+        // }
+    }
+}
+
+mod tests {
+    use crate::debug_renderer::DebugRenderer;
+    use crate::nodebuilder::LazyNodes;
+
+    use crate as dioxus;
+    use dioxus::prelude::*;
+    #[test]
+    fn iterate_nodes() {
+        let rs = rsx! {
+            Fragment {
+                Fragment {
+                    Fragment {
+                        Fragment {
+                            h1 {"abc1"}
+                        }
+                        h2 {"abc2"}
+                    }
+                    h3 {"abc3"}
+                }
+                h4 {"abc4"}
+            }
+        };
+    }
+}

+ 0 - 695
packages/core/src/patch.rs

@@ -1,695 +0,0 @@
-//! Changelist
-//! ----------
-//!
-//! This module exposes the "changelist" object which allows 3rd party implementors to handle diffs to the virtual dom.
-//!
-//! # Design
-//! ---
-//! In essence, the changelist object connects a diff of two vdoms to the actual edits required to update the output renderer.
-//!
-//! This abstraction relies on the assumption that the final renderer accepts a tree of elements. For most target platforms,
-//! this is an appropriate abstraction .
-//!
-//! During the diff phase, the change list is built. Once the diff phase is over, the change list is finished and returned back
-//! to the renderer. The renderer is responsible for propogating the updates to the final display.
-//!
-//! Because the change list references data internal to the vdom, it needs to be consumed by the renderer before the vdom
-//! can continue to work. This means once a change list is generated, it should be consumed as fast as possible, otherwise the
-//! dom will be blocked from progressing. This is enforced by lifetimes on the returend changelist object.
-//!
-//! # Known Issues
-//! ----
-//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom) - solvable by the renderer
-
-use crate::innerlude::ScopeIdx;
-
-pub type EditList<'src> = Vec<Edit<'src>>;
-
-/// The `Edit` represents a single modifcation of the renderer tree.
-/// todo @jon, go through and make certain fields static. tag names should be known at compile time
-#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
-#[cfg_attr(feature = "serde", serde(tag = "type"))]
-#[derive(Debug)]
-pub enum Edit<'src_bump> {
-    // ========================================================
-    // Common Ops: The most common operation types
-    // ========================================================
-    SetText {
-        text: &'src_bump str,
-    },
-    SetClass {
-        class_name: &'src_bump str,
-    },
-    CreateTextNode {
-        text: &'src_bump str,
-    },
-    CreateElement {
-        // todo - make static?
-        tag_name: &'src_bump str,
-    },
-    CreateElementNs {
-        // todo - make static?
-        tag_name: &'src_bump str,
-        // todo - make static?
-        ns: &'src_bump str,
-    },
-
-    // ========================================================
-    // Attributes
-    // ========================================================
-    SetAttribute {
-        name: &'src_bump str,
-        value: &'src_bump str,
-    },
-    RemoveAttribute {
-        name: &'src_bump str,
-    },
-    RemoveChild {
-        n: u32,
-    },
-
-    // ============================================================
-    // Event Listeners: Event types and IDs used to update the VDOM
-    // ============================================================
-    NewListener {
-        // todo - make static?
-        event: &'src_bump str,
-        scope: ScopeIdx,
-        id: usize,
-    },
-    UpdateListener {
-        // todo - make static?
-        event: &'src_bump str,
-        scope: ScopeIdx,
-        id: usize,
-    },
-    RemoveListener {
-        // todo - make static?
-        event: &'src_bump str,
-    },
-
-    // ========================================================
-    // Cached Roots: The mount point for individual components
-    //               Allows quick traversal to cached entrypoints
-    // ========================================================
-    // push a known node on to the stack
-    TraverseToKnown {
-        node: u32,
-        // node: ScopeIdx,
-    },
-    // Add the current top of the stack to the known nodes
-    MakeKnown {
-        node: u32,
-        // node: ScopeIdx,
-    },
-    // Remove the current top of the stack from the known nodes
-    RemoveKnown,
-
-    // ========================================================
-    // Stack OPs: Operations for manipulating the stack machine
-    // ========================================================
-    PushReverseChild {
-        n: u32,
-    },
-    PopPushChild {
-        n: u32,
-    },
-    Pop,
-    AppendChild,
-    RemoveSelfAndNextSiblings {},
-    ReplaceWith,
-    SaveChildrenToTemporaries {
-        temp: u32,
-        start: u32,
-        end: u32,
-    },
-    PushChild {
-        n: u32,
-    },
-    PushTemporary {
-        temp: u32,
-    },
-    InsertBefore,
-    PopPushReverseChild {
-        n: u32,
-    },
-}
-
-/// The edit machine represents a stream of differences between two component trees.
-///
-/// This struct is interesting in that it keeps track of differences by borrowing
-/// from the source rather than writing to a new buffer. This means that the virtual dom
-/// *cannot* be updated while this machine is in existence without "unsafe".
-///
-/// This unsafety is handled by methods on the virtual dom and is not exposed via lib code.
-pub struct EditMachine<'lock> {
-    pub traversal: Traversal,
-    next_temporary: u32,
-    forcing_new_listeners: bool,
-    pub cur_height: u32,
-
-    // // if the current node is a "known" node
-    // // any actions that modify this node should update the mapping
-    // current_known: Option<u32>,
-    pub emitter: EditList<'lock>,
-}
-
-impl<'lock> EditMachine<'lock> {
-    pub fn new() -> Self {
-        Self {
-            // current_known: None,
-            traversal: Traversal::new(),
-            cur_height: 0,
-            next_temporary: 0,
-            forcing_new_listeners: false,
-            emitter: EditList::<'lock>::default(),
-        }
-    }
-}
-
-// ===================================
-//  Traversal Methods
-// ===================================
-impl<'src> EditMachine<'src> {
-    pub fn go_down(&mut self) {
-        self.traversal.down();
-    }
-
-    pub fn go_down_to_child(&mut self, index: usize) {
-        self.traversal.down();
-        self.traversal.sibling(index);
-    }
-
-    pub fn go_down_to_reverse_child(&mut self, index: usize) {
-        self.traversal.down();
-        self.traversal.reverse_sibling(index);
-    }
-
-    pub fn go_up(&mut self) {
-        self.traversal.up();
-    }
-
-    pub fn go_to_sibling(&mut self, index: usize) {
-        self.traversal.sibling(index);
-    }
-
-    pub fn go_to_temp_sibling(&mut self, temp: u32) {
-        self.traversal.up();
-        self.traversal.down_to_temp(temp);
-    }
-
-    pub fn go_down_to_temp_child(&mut self, temp: u32) {
-        self.traversal.down_to_temp(temp);
-    }
-
-    pub fn commit_traversal(&mut self) {
-        if self.traversal.is_committed() {
-            return;
-        }
-
-        for mv in self.traversal.commit() {
-            match mv {
-                MoveTo::Parent => self.emitter.push(Edit::Pop {}),
-                MoveTo::Child(n) => self.emitter.push(Edit::PushChild { n }),
-                MoveTo::ReverseChild(n) => self.emitter.push(Edit::PushReverseChild { n }),
-                MoveTo::Sibling(n) => self.emitter.push(Edit::PopPushChild { n }),
-                MoveTo::ReverseSibling(n) => self.emitter.push(Edit::PopPushReverseChild { n }),
-                MoveTo::TempChild(temp) => self.emitter.push(Edit::PushTemporary { temp }),
-            }
-        }
-    }
-
-    pub fn traversal_is_committed(&self) -> bool {
-        self.traversal.is_committed()
-    }
-}
-
-// ===================================
-//  Stack methods
-// ===================================
-impl<'a> EditMachine<'a> {
-    pub fn next_temporary(&self) -> u32 {
-        self.next_temporary
-    }
-
-    pub fn set_next_temporary(&mut self, next_temporary: u32) {
-        self.next_temporary = next_temporary;
-    }
-
-    pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 {
-        debug_assert!(self.traversal_is_committed());
-        debug_assert!(start < end);
-        let temp_base = self.next_temporary;
-        self.next_temporary = temp_base + (end - start) as u32;
-        self.emitter.push(Edit::SaveChildrenToTemporaries {
-            temp: temp_base,
-            start: start as u32,
-            end: end as u32,
-        });
-        temp_base
-    }
-
-    pub fn push_temporary(&mut self, temp: u32) {
-        debug_assert!(self.traversal_is_committed());
-
-        self.emitter.push(Edit::PushTemporary { temp });
-    }
-
-    pub fn remove_child(&mut self, child: usize) {
-        debug_assert!(self.traversal_is_committed());
-
-        self.emitter.push(Edit::RemoveChild { n: child as u32 })
-    }
-
-    pub fn insert_before(&mut self) {
-        debug_assert!(self.traversal_is_committed());
-
-        self.emitter.push(Edit::InsertBefore {})
-    }
-
-    pub fn set_text(&mut self, text: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::SetText { text });
-    }
-
-    pub fn remove_self_and_next_siblings(&mut self) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::RemoveSelfAndNextSiblings {});
-    }
-
-    pub fn replace_with(&mut self) {
-        debug_assert!(self.traversal_is_committed());
-        // debug!("emit: replace_with()");
-        // if let Some(id) = self.current_known {
-        //     // update mapping
-        //     self.emitter.push(Edit::MakeKnown{node: id});
-        //     self.current_known = None;
-        // }
-        // self.emitter.replace_with();
-        self.emitter.push(Edit::ReplaceWith {});
-    }
-
-    pub fn set_attribute(&mut self, name: &'a str, value: &'a str, is_namespaced: bool) {
-        debug_assert!(self.traversal_is_committed());
-
-        if name == "class" && !is_namespaced {
-            self.emitter.push(Edit::SetClass { class_name: value });
-        } else {
-            self.emitter.push(Edit::SetAttribute { name, value });
-        }
-    }
-
-    pub fn remove_attribute(&mut self, name: &'a str) {
-        self.emitter.push(Edit::RemoveAttribute { name });
-    }
-
-    pub fn append_child(&mut self) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::AppendChild {});
-    }
-
-    pub fn create_text_node(&mut self, text: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::CreateTextNode { text });
-    }
-
-    pub fn create_element(&mut self, tag_name: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::CreateElement { tag_name });
-    }
-
-    pub fn create_element_ns(&mut self, tag_name: &'a str, ns: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::CreateElementNs { tag_name, ns });
-    }
-
-    pub fn push_force_new_listeners(&mut self) -> bool {
-        let old = self.forcing_new_listeners;
-        self.forcing_new_listeners = true;
-        old
-    }
-
-    pub fn pop_force_new_listeners(&mut self, previous: bool) {
-        debug_assert!(self.forcing_new_listeners);
-        self.forcing_new_listeners = previous;
-    }
-
-    pub fn new_event_listener(&mut self, event: &'a str, scope: ScopeIdx, id: usize) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::NewListener { event, scope, id });
-    }
-
-    pub fn update_event_listener(&mut self, event: &'a str, scope: ScopeIdx, id: usize) {
-        debug_assert!(self.traversal_is_committed());
-        if self.forcing_new_listeners {
-            self.new_event_listener(event, scope, id);
-            return;
-        }
-
-        self.emitter.push(Edit::NewListener { event, scope, id });
-    }
-
-    pub fn remove_event_listener(&mut self, event: &'a str) {
-        debug_assert!(self.traversal_is_committed());
-        self.emitter.push(Edit::RemoveListener { event });
-    }
-
-    pub fn save_known_root(&mut self, id: u32) {
-        log::debug!("emit: save_known_root({:?})", id);
-        self.emitter.push(Edit::MakeKnown { node: id })
-    }
-
-    pub fn load_known_root(&mut self, id: u32) {
-        log::debug!("emit: TraverseToKnown({:?})", id);
-        self.emitter.push(Edit::TraverseToKnown { node: id })
-    }
-}
-
-// Keeps track of where we are moving in a DOM tree, and shortens traversal
-// paths between mutations to their minimal number of operations.
-#[derive(Clone, Copy, Debug, PartialEq, Eq)]
-pub enum MoveTo {
-    /// Move from the current node up to its parent.
-    Parent,
-
-    /// Move to the current node's n^th child.
-    Child(u32),
-
-    /// Move to the current node's n^th from last child.
-    ReverseChild(u32),
-
-    /// Move to the n^th sibling. Not relative from the current
-    /// location. Absolute indexed within all of the current siblings.
-    Sibling(u32),
-
-    /// Move to the n^th from last sibling. Not relative from the current
-    /// location. Absolute indexed within all of the current siblings.
-    ReverseSibling(u32),
-
-    /// Move down to the given saved temporary child.
-    TempChild(u32),
-}
-
-#[derive(Debug)]
-pub struct Traversal {
-    uncommitted: Vec<MoveTo>,
-}
-
-impl Traversal {
-    /// Construct a new `Traversal` with its internal storage backed by the
-    /// given bump arena.
-    pub fn new() -> Traversal {
-        Traversal {
-            uncommitted: Vec::with_capacity(32),
-        }
-    }
-
-    /// Move the traversal up in the tree.
-    pub fn up(&mut self) {
-        match self.uncommitted.last() {
-            Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::Parent);
-            }
-            Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) | Some(MoveTo::ReverseChild(_)) => {
-                self.uncommitted.pop();
-                // And we're back at the parent.
-            }
-            _ => {
-                self.uncommitted.push(MoveTo::Parent);
-            }
-        }
-    }
-
-    /// Move the traversal down in the tree to the first child of the current
-    /// node.
-    pub fn down(&mut self) {
-        if let Some(&MoveTo::Parent) = self.uncommitted.last() {
-            self.uncommitted.pop();
-            self.sibling(0);
-        } else {
-            self.uncommitted.push(MoveTo::Child(0));
-        }
-    }
-
-    /// Move the traversal to the n^th sibling.
-    pub fn sibling(&mut self, index: usize) {
-        let index = index as u32;
-        match self.uncommitted.last_mut() {
-            Some(MoveTo::Sibling(ref mut n)) | Some(MoveTo::Child(ref mut n)) => {
-                *n = index;
-            }
-            Some(MoveTo::ReverseSibling(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::Sibling(index));
-            }
-            Some(MoveTo::TempChild(_)) | Some(MoveTo::ReverseChild(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::Child(index))
-            }
-            _ => {
-                self.uncommitted.push(MoveTo::Sibling(index));
-            }
-        }
-    }
-
-    /// Move the the n^th from last sibling.
-    pub fn reverse_sibling(&mut self, index: usize) {
-        let index = index as u32;
-        match self.uncommitted.last_mut() {
-            Some(MoveTo::ReverseSibling(ref mut n)) | Some(MoveTo::ReverseChild(ref mut n)) => {
-                *n = index;
-            }
-            Some(MoveTo::Sibling(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::ReverseSibling(index));
-            }
-            Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) => {
-                self.uncommitted.pop();
-                self.uncommitted.push(MoveTo::ReverseChild(index))
-            }
-            _ => {
-                self.uncommitted.push(MoveTo::ReverseSibling(index));
-            }
-        }
-    }
-
-    /// Go to the given saved temporary.
-    pub fn down_to_temp(&mut self, temp: u32) {
-        match self.uncommitted.last() {
-            Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
-                self.uncommitted.pop();
-            }
-            Some(MoveTo::Parent)
-            | Some(MoveTo::TempChild(_))
-            | Some(MoveTo::Child(_))
-            | Some(MoveTo::ReverseChild(_))
-            | None => {
-                // Can't remove moves to parents since we rely on their stack
-                // pops.
-            }
-        }
-        self.uncommitted.push(MoveTo::TempChild(temp));
-    }
-
-    /// Are all the traversal's moves committed? That is, are there no moves
-    /// that have *not* been committed yet?
-    #[inline]
-    pub fn is_committed(&self) -> bool {
-        self.uncommitted.is_empty()
-    }
-
-    /// Commit this traversals moves and return the optimized path from the last
-    /// commit.
-    #[inline]
-    pub fn commit(&mut self) -> std::vec::Drain<'_, MoveTo> {
-        self.uncommitted.drain(..)
-    }
-
-    #[inline]
-    pub fn reset(&mut self) {
-        self.uncommitted.clear();
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_traversal() {
-        fn t<F>(f: F) -> Box<dyn FnMut(&mut Traversal)>
-        where
-            F: 'static + FnMut(&mut Traversal),
-        {
-            Box::new(f) as _
-        }
-
-        for (mut traverse, expected_moves) in vec![
-            (
-                t(|t| {
-                    t.down();
-                }),
-                vec![MoveTo::Child(0)],
-            ),
-            (
-                t(|t| {
-                    t.up();
-                }),
-                vec![MoveTo::Parent],
-            ),
-            (
-                t(|t| {
-                    t.sibling(42);
-                }),
-                vec![MoveTo::Sibling(42)],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.up();
-                }),
-                vec![],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.sibling(2);
-                    t.up();
-                }),
-                vec![],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.sibling(3);
-                }),
-                vec![MoveTo::Child(3)],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.sibling(4);
-                    t.sibling(8);
-                }),
-                vec![MoveTo::Child(8)],
-            ),
-            (
-                t(|t| {
-                    t.sibling(1);
-                    t.sibling(1);
-                }),
-                vec![MoveTo::Sibling(1)],
-            ),
-            (
-                t(|t| {
-                    t.reverse_sibling(3);
-                }),
-                vec![MoveTo::ReverseSibling(3)],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.reverse_sibling(3);
-                }),
-                vec![MoveTo::ReverseChild(3)],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.reverse_sibling(3);
-                    t.up();
-                }),
-                vec![],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.reverse_sibling(3);
-                    t.reverse_sibling(6);
-                }),
-                vec![MoveTo::ReverseChild(6)],
-            ),
-            (
-                t(|t| {
-                    t.up();
-                    t.reverse_sibling(3);
-                    t.reverse_sibling(6);
-                }),
-                vec![MoveTo::Parent, MoveTo::ReverseSibling(6)],
-            ),
-            (
-                t(|t| {
-                    t.up();
-                    t.sibling(3);
-                    t.sibling(6);
-                }),
-                vec![MoveTo::Parent, MoveTo::Sibling(6)],
-            ),
-            (
-                t(|t| {
-                    t.sibling(3);
-                    t.sibling(6);
-                    t.up();
-                }),
-                vec![MoveTo::Parent],
-            ),
-            (
-                t(|t| {
-                    t.reverse_sibling(3);
-                    t.reverse_sibling(6);
-                    t.up();
-                }),
-                vec![MoveTo::Parent],
-            ),
-            (
-                t(|t| {
-                    t.down();
-                    t.down_to_temp(3);
-                }),
-                vec![MoveTo::Child(0), MoveTo::TempChild(3)],
-            ),
-            (
-                t(|t| {
-                    t.down_to_temp(3);
-                    t.sibling(5);
-                }),
-                vec![MoveTo::Child(5)],
-            ),
-            (
-                t(|t| {
-                    t.down_to_temp(3);
-                    t.reverse_sibling(5);
-                }),
-                vec![MoveTo::ReverseChild(5)],
-            ),
-            (
-                t(|t| {
-                    t.down_to_temp(3);
-                    t.up();
-                }),
-                vec![],
-            ),
-            (
-                t(|t| {
-                    t.sibling(2);
-                    t.up();
-                    t.down_to_temp(3);
-                }),
-                vec![MoveTo::Parent, MoveTo::TempChild(3)],
-            ),
-            (
-                t(|t| {
-                    t.up();
-                    t.down_to_temp(3);
-                }),
-                vec![MoveTo::Parent, MoveTo::TempChild(3)],
-            ),
-        ] {
-            let mut traversal = Traversal::new();
-            traverse(&mut traversal);
-            let actual_moves: Vec<_> = traversal.commit().collect();
-            assert_eq!(actual_moves, expected_moves);
-        }
-    }
-}

+ 80 - 67
packages/core/src/virtual_dom.rs

@@ -24,7 +24,7 @@ use bumpalo::Bump;
 use generational_arena::Arena;
 use std::{
     any::{Any, TypeId},
-    cell::RefCell,
+    cell::{Cell, RefCell},
     collections::{HashMap, HashSet, VecDeque},
     fmt::Debug,
     future::Future,
@@ -63,6 +63,17 @@ pub struct VirtualDom {
     _root_prop_type: std::any::TypeId,
 }
 
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub struct RealDomNode(pub u32);
+impl RealDomNode {
+    pub fn new(id: u32) -> Self {
+        Self(id)
+    }
+    pub fn empty() -> Self {
+        Self(u32::MIN)
+    }
+}
+
 // ======================================
 // Public Methods for the VirtualDom
 // ======================================
@@ -98,7 +109,7 @@ impl VirtualDom {
     ///
     /// let dom = VirtualDom::new(Example);
     /// ```
-    pub fn new(root: impl Fn(Context<()>) -> VNode + 'static) -> Self {
+    pub fn new(root: FC<()>) -> Self {
         Self::new_with_props(root, ())
     }
 
@@ -130,10 +141,7 @@ impl VirtualDom {
     ///
     /// let dom = VirtualDom::new(Example);
     /// ```
-    pub fn new_with_props<P: Properties + 'static>(
-        root: impl Fn(Context<P>) -> VNode + 'static,
-        root_props: P,
-    ) -> Self {
+    pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
         let components = ScopeArena::new(Arena::new());
 
         // Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
@@ -174,11 +182,17 @@ impl VirtualDom {
             _root_prop_type: TypeId::of::<P>(),
         }
     }
+}
 
+// ======================================
+// Private Methods for the VirtualDom
+// ======================================
+impl VirtualDom {
     /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
     /// Currently this doesn't do what we want it to do
-    pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
+    pub fn rebuild<'s, Dom: RealDom>(&'s mut self, realdom: &mut Dom) -> Result<()> {
         let mut diff_machine = DiffMachine::new(
+            realdom,
             self.components.clone(),
             self.base_scope,
             self.event_queue.clone(),
@@ -195,14 +209,8 @@ impl VirtualDom {
 
         self.progress_completely(&mut diff_machine)?;
 
-        Ok(diff_machine.consume())
+        Ok(())
     }
-}
-
-// ======================================
-// Private Methods for the VirtualDom
-// ======================================
-impl VirtualDom {
     /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
     ///  
     /// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual
@@ -246,26 +254,34 @@ impl VirtualDom {
     // but the guarantees provide a safe, fast, and efficient abstraction for the VirtualDOM updating framework.
     //
     // A good project would be to remove all unsafe from this crate and move the unsafety into safer abstractions.
-    pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList> {
-        let id = event.component_id.clone();
+    pub fn progress_with_event<Dom: RealDom>(
+        &mut self,
+        realdom: &mut Dom,
+        trigger: EventTrigger,
+    ) -> Result<()> {
+        let id = trigger.component_id.clone();
 
-        self.components.try_get_mut(id)?.call_listener(event)?;
+        self.components.try_get_mut(id)?.call_listener(trigger)?;
 
-        let mut diff_machine =
-            DiffMachine::new(self.components.clone(), id, self.event_queue.clone());
+        let mut diff_machine = DiffMachine::new(
+            realdom,
+            self.components.clone(),
+            id,
+            self.event_queue.clone(),
+        );
 
         self.progress_completely(&mut diff_machine)?;
 
-        Ok(diff_machine.consume())
+        Ok(())
     }
 
     /// Consume the event queue, descending depth-first.
     /// Only ever run each component once.
     ///
     /// The DiffMachine logs its progress as it goes which might be useful for certain types of renderers.
-    pub(crate) fn progress_completely<'s>(
+    pub(crate) fn progress_completely<'s, Dom: RealDom>(
         &'s mut self,
-        diff_machine: &'_ mut DiffMachine<'s>,
+        diff_machine: &'_ mut DiffMachine<'s, Dom>,
     ) -> Result<()> {
         // Add this component to the list of components that need to be difed
         // #[allow(unused_assignments)]
@@ -302,7 +318,9 @@ impl VirtualDom {
             cur_component.run_scope()?;
             // diff_machine.change_list.load_known_root(1);
 
-            diff_machine.diff_node(cur_component.old_frame(), cur_component.next_frame());
+            let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
+            // let (old, new) = cur_component.get_frames_mut();
+            diff_machine.diff_node(old, new);
 
             // cur_height = cur_component.height;
 
@@ -376,7 +394,12 @@ pub struct Scope {
     // - is self-refenrential and therefore needs to point into the bump
     // Stores references into the listeners attached to the vnodes
     // NEEDS TO BE PRIVATE
-    pub(crate) listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
+    pub(crate) listeners: RefCell<Vec<(*const Cell<RealDomNode>, *const dyn Fn(VirtualEvent))>>,
+    // pub(crate) listeners: RefCell<nohash_hasher::IntMap<u32, *const dyn Fn(VirtualEvent)>>,
+    // pub(crate) listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
+    // pub(crate) listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
+    // NoHashMap<RealDomNode, <*const dyn Fn(VirtualEvent)>
+    // pub(crate) listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
 }
 
 // We need to pin the hook so it doesn't move as we initialize the list of hooks
@@ -429,7 +452,7 @@ impl Scope {
         let child_nodes = unsafe { std::mem::transmute(child_nodes) };
 
         Self {
-            child_nodes: child_nodes,
+            child_nodes,
             caller,
             parent,
             arena_idx,
@@ -469,12 +492,12 @@ impl Scope {
         self.frames.next().bump.reset();
 
         // Remove all the outdated listeners
-        //
-        self.listeners
-            .try_borrow_mut()
-            .ok()
-            .ok_or(Error::FatalInternal("Borrowing listener failed"))?
-            .drain(..);
+        self.listeners.borrow_mut().clear();
+        // self.listeners
+        //     .try_borrow_mut()
+        //     .ok()
+        //     .ok_or(Error::FatalInternal("Borrowing listener failed"))?
+        //     .drain(..);
 
         *self.hookidx.borrow_mut() = 0;
 
@@ -508,25 +531,35 @@ impl Scope {
     // The listener list will be completely drained because the next frame will write over previous listeners
     pub fn call_listener(&mut self, trigger: EventTrigger) -> Result<()> {
         let EventTrigger {
-            listener_id, event, ..
+            real_node_id,
+            event,
+            ..
         } = trigger;
-        //
+
+        // todo: implement scanning for outdated events
+
+        // Convert the raw ptr into an actual object
+        // This operation is assumed to be safe
+
+        log::debug!("Calling listeners! {:?}", self.listeners.borrow().len());
+        let listners = self.listeners.borrow();
+        let (_, listener) = listners
+            .iter()
+            .find(|(domptr, _)| {
+                let p = unsafe { &**domptr };
+                p.get() == real_node_id
+            })
+            .expect(&format!(
+                "Failed to find real node with ID {:?}",
+                real_node_id
+            ));
+
+        // TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster!
         unsafe {
-            // Convert the raw ptr into an actual object
-            // This operation is assumed to be safe
-            let listener_fn = self
-                .listeners
-                .try_borrow()
-                .ok()
-                .ok_or(Error::FatalInternal("Borrowing listener failed"))?
-                .get(listener_id as usize)
-                .ok_or(Error::FatalInternal("Event should exist if triggered"))?
-                .as_ref()
-                .ok_or(Error::FatalInternal("Raw event ptr is invalid"))?;
-
-            // Run the callback with the user event
+            let listener_fn = &**listener;
             listener_fn(event);
         }
+
         Ok(())
     }
 
@@ -745,7 +778,7 @@ Any function prefixed with "use" should not be called conditionally.
     }
 
     /// There are hooks going on here!
-    fn use_context<T: 'static>(&self) -> &'src Rc<T> {
+    fn use_context<T: 'static>(&self) -> &'src T {
         self.try_use_context().unwrap()
     }
 
@@ -858,26 +891,6 @@ impl PartialOrd for HeightMarker {
     }
 }
 
-// NodeCtx is used to build VNodes in the component's memory space.
-// This struct adds metadata to the final VNode about listeners, attributes, and children
-#[derive(Clone)]
-pub struct NodeCtx<'a> {
-    pub scope_ref: &'a Scope,
-    pub listener_id: RefCell<usize>,
-}
-
-impl<'a> NodeCtx<'a> {
-    pub fn bump(&self) -> &'a Bump {
-        &self.scope_ref.cur_frame().bump
-    }
-}
-
-impl Debug for NodeCtx<'_> {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        Ok(())
-    }
-}
-
 #[derive(Debug, PartialEq, Hash)]
 pub struct ContextId {
     // Which component is the scope in

+ 30 - 0
packages/html-namespace/examples/poc.rs

@@ -0,0 +1,30 @@
+//! POC: Planning the layout of a single element type
+//!
+//!
+//! The ultimate goal with a dedicated namespace is three-fold:
+//! - compile-time correct templates preventing misuse of elemnents
+//! - deep integration of DSL with IDE
+//!
+//!
+//!
+
+struct NodeCtx {}
+
+struct div<'a>(&NodeCtx);
+impl<'a> div<'a> {
+    fn new(cx: &NodeCtx) -> Self {
+        div(cx)
+    }
+}
+
+fn main() {}
+
+fn factory(
+    // this is your mom
+    cx: &NodeCtx,
+) {
+    div::new(cx);
+    rsx! {
+        div {}
+    }
+}

+ 4 - 17
packages/web/Cargo.toml

@@ -23,20 +23,15 @@ wasm-bindgen-test = "0.3.21"
 once_cell = "1.7.2"
 atoms = { path="../atoms" }
 
-# wasm-bindgen = "0.2.70"
-# futures = "0.3.12"
-# html-validation = { path = "../html-validation", version = "0.1.1" }
-
 async-channel = "1.6.1"
-wee_alloc = "0.4.5"
-# futures-lite = "1.11.3"
+nohash-hasher = "0.2.0"
+anyhow = "1.0.41"
 
 [dependencies.web-sys]
 version = "0.3.50"
 features = [
     "Comment",
     "Document",
-    # "DataTransfer",
     "Element",
     "HtmlElement",
     "HtmlInputElement",
@@ -63,24 +58,16 @@ features = [
     "DocumentType",
     "CharacterData",
     "HtmlOptionElement",
-
 ]
 
 [profile.release]
 lto = true
 opt-level = 's'
 
-# debug = true
-
-# [profile.release]
-
-
 [lib]
 crate-type = ["cdylib", "rlib"]
 
 [dev-dependencies]
+im-rc = "15.0.0"
+rand = { version="0.8.4", features=["small_rng"] }
 uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] }
-
-[[example]]
-name = "todomvc"
-path = "./examples/todomvc/main.rs"

+ 10 - 9
packages/web/examples/basic.rs

@@ -1,5 +1,6 @@
 //! Basic example that renders a simple VNode to the browser.
 
+use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
 use dioxus_web::*;
 
@@ -50,15 +51,15 @@ static DocExamples: FC<()> = |ctx| {
         }
     };
 
-    rsx! {
-        div {}
-        h1 {}
-        {""}
-        "asbasd"
-        dioxus::Fragment {
-            //
-        }
-    }
+    // rsx! {
+    //     div {}
+    //     h1 {}
+    //     {""}
+    //     "asbasd"
+    //     dioxus::Fragment {
+    //         //
+    //     }
+    // }
 
     ctx.render(rsx! {
         div {

+ 9 - 10
packages/web/examples/context.rs

@@ -2,16 +2,17 @@
 //! --------------------
 //! This example demonstrates how to use the raw context api for sharing state throughout the VirtualDOM Tree.
 //! A custom context must be its own unique type - otherwise use_context will fail. A context may be c
-//! 
-//! 
-//! 
-//! 
-//! 
-//! 
-//! 
-//! 
+//!
+//!
+//!
+//!
+//!
+//!
+//!
+//!
 
 use dioxus_core::prelude::*;
+use dioxus_core as dioxus;
 use dioxus_web::WebsysRenderer;
 
 fn main() {
@@ -20,7 +21,6 @@ fn main() {
     wasm_bindgen_futures::spawn_local(WebsysRenderer::start(Example));
 }
 
-
 #[derive(Debug)]
 struct CustomContext([&'static str; 3]);
 
@@ -46,7 +46,6 @@ static Example: FC<()> = |ctx| {
     })
 };
 
-
 #[derive(Props, PartialEq)]
 struct ButtonProps {
     id: u8,

+ 1 - 0
packages/web/examples/deep.rs

@@ -1,5 +1,6 @@
 use std::rc::Rc;
 
+use dioxus_core as dioxus;
 use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
 
 fn main() {

+ 3 - 2
packages/web/examples/demoday.rs

@@ -1,3 +1,4 @@
+use dioxus_core as dioxus;
 use dioxus_web::{dioxus::prelude::*, WebsysRenderer};
 
 fn main() {
@@ -10,7 +11,7 @@ fn App(ctx: Context<()>) -> VNode {
     ctx.render(rsx! {
         main { class: "dark:bg-gray-800 bg-white relative h-screen"
             NavBar {}
-            {(0..10).map(|f| rsx!{ Landing {} })}
+            {(0..10).map(|f| rsx!(Landing { key: "{f}" }))}
         }
     })
 }
@@ -61,7 +62,7 @@ fn Landing(ctx: Context<()>) -> VNode {
             div { class: "container mx-auto px-6 flex flex-col justify-between items-center relative py-8"
                 div { class: "flex flex-col"
                     h1 { class: "font-light w-full uppercase text-center text-4xl sm:text-5xl dark:text-white text-gray-800"
-                        "The React Framework for Production"
+                        "The Dioxus Framework for Production"
                     }
                     h2{ class: "font-light max-w-2xl mx-auto w-full text-xl dark:text-white text-gray-500 text-center py-8"
                         "Next.js gives you the best developer experience with all the features you need for production: \n

+ 0 - 4
packages/web/examples/derive.rs

@@ -8,10 +8,6 @@ fn main() {
     wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
 }
 
-// Use `wee_alloc` as the global allocator.
-#[global_allocator]
-static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
-
 fn App(ctx: Context<()>) -> VNode {
     let cansee = use_state_new(&ctx, || false);
     rsx! { in ctx,

+ 11 - 11
packages/web/examples/events.rs

@@ -5,17 +5,17 @@ use dioxus_core::prelude::*;
 fn main() {}
 
 fn autocomplete() {
-    let handler = move |evt| {
-        let r = evt.alt_key();
-        if evt.alt_key() {}
-    };
+    // let handler = move |evt| {
+    //     let r = evt.alt_key();
+    //     if evt.alt_key() {}
+    // };
 
-    let g = rsx! {
-        button {
-            button {
-                onclick: {handler}
-            }
-        }
+    // let g = rsx! {
+    //     button {
+    //         button {
+    //             onclick: {handler}
+    //         }
+    //     }
 
-    };
+    // };
 }

+ 184 - 0
packages/web/examples/framework_benchmark.rs

@@ -0,0 +1,184 @@
+//! JS Framework Benchmark
+//! ----------------------
+//!
+//! This example is used in the JS framework benchmarking tool to compare Dioxus' performance with other frontend frameworks.
+//!
+//!
+//!
+
+use std::rc::Rc;
+
+use dioxus::events::on::MouseEvent;
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_web::WebsysRenderer;
+
+fn main() {
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    console_error_panic_hook::set_once();
+    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
+}
+
+// We use a special immutable hashmap to make hashmap operations efficient
+type RowList = im_rc::HashMap<usize, Rc<str>, nohash_hasher::BuildNoHashHasher<usize>>;
+
+static App: FC<()> = |cx| {
+    let (items, set_items) = use_state(&cx, || RowList::default());
+    let (selection, set_selection) = use_state(&cx, || None as Option<usize>);
+
+    let create_rendered_rows = move |from, num| move |_| set_items(create_row_list(from, num));
+
+    let append_1_000_rows =
+        move |_| set_items(create_row_list(items.len(), 1000).union(items.clone()));
+
+    let update_every_10th_row = move |_| {
+        let mut new_items = items.clone();
+        let mut small_rng = SmallRng::from_entropy();
+        new_items
+            .iter_mut()
+            .step_by(10)
+            .for_each(|(_, val)| *val = create_new_row_label(&mut small_rng));
+        set_items(new_items);
+    };
+    let clear_rows = move |_| set_items(RowList::default());
+
+    let swap_rows = move |_| {
+        // this looks a bit ugly because we're using a hashmap instead of a vec
+        if items.len() > 998 {
+            let mut new_items = items.clone();
+            let a = new_items.get(&0).unwrap().clone();
+            *new_items.get_mut(&0).unwrap() = new_items.get(&998).unwrap().clone();
+            *new_items.get_mut(&998).unwrap() = a;
+            set_items(new_items);
+        }
+    };
+
+    let rows = items.iter().map(|(key, value)| {
+        rsx!(Row {
+            key: "{key}",
+            row_id: *key as usize,
+            label: value.clone(),
+        })
+    });
+
+    cx.render(rsx! {
+        div { class: "container"
+            div { class: "jumbotron"
+                div { class: "row"
+                    div { class: "col-md-6", h1 { "Dioxus" } }
+                    div { class: "col-md-6"
+                        div { class: "row"
+                            ActionButton { name: "Create 1,000 rows", id: "run", action: create_rendered_rows(0, 1_000) }
+                            ActionButton { name: "Create 10,000 rows", id: "runlots", action: create_rendered_rows(0, 10_000) }
+                            ActionButton { name: "Append 1,000 rows", id: "add", action: append_1_000_rows }
+                            ActionButton { name: "Update every 10th row", id: "update", action: update_every_10th_row, }
+                            ActionButton { name: "Clear", id: "clear", action: clear_rows }
+                            ActionButton { name: "Swap rows", id: "swaprows", action: swap_rows }
+                        }
+                    }
+                }
+            }
+            table { 
+                tbody {
+                    {rows}
+                }
+             }
+            span {}
+        }
+    })
+};
+
+#[derive(Props)]
+struct ActionButtonProps<F: Fn(Rc<dyn MouseEvent>)> {
+    name: &'static str,
+    id: &'static str,
+    action: F,
+}
+fn ActionButton<F: Fn(Rc<dyn MouseEvent>)>(cx: Context<ActionButtonProps<F>>) -> VNode {
+    cx.render(rsx! {
+        div { class: "col-sm-6 smallpad"
+            button {class:"btn btn-primary btn-block", type: "button", id: "{cx.id}",  onclick: {&cx.action},
+                "{cx.name}"
+            }
+        }
+    })
+}
+
+
+#[derive(PartialEq, Props)]
+struct RowProps {
+    row_id: usize,
+    label: Rc<str>,
+}
+fn Row<'a>(cx: Context<'a, RowProps>) -> VNode {
+    cx.render(rsx! {
+        tr {
+            td { class:"col-md-1", "{cx.row_id}" }
+            td { class:"col-md-1", onclick: move |_| { /* run onselect */ }
+                a { class: "lbl", "{cx.label}" }
+            }
+            td { class: "col-md-1"
+                a { class: "remove", onclick: move |_| {/* remove */}
+                    span { class: "glyphicon glyphicon-remove remove" aria_hidden: "true" }
+                }
+            }
+            td { class: "col-md-6" }
+        }
+    })
+}
+
+use rand::prelude::*;
+fn create_new_row_label(rng: &mut SmallRng) -> Rc<str> {
+    let mut label = String::new();
+    label.push_str(ADJECTIVES.choose(rng).unwrap());
+    label.push(' ');
+    label.push_str(COLOURS.choose(rng).unwrap());
+    label.push(' ');
+    label.push_str(NOUNS.choose(rng).unwrap());
+    Rc::from(label)
+}
+
+fn create_row_list(from: usize, num: usize) -> RowList {
+    let mut small_rng = SmallRng::from_entropy();
+    (from..num + from)
+        .map(|f| (f, create_new_row_label(&mut small_rng)))
+        .collect::<RowList>()
+}
+
+static ADJECTIVES: &[&str] = &[
+    "pretty",
+    "large",
+    "big",
+    "small",
+    "tall",
+    "short",
+    "long",
+    "handsome",
+    "plain",
+    "quaint",
+    "clean",
+    "elegant",
+    "easy",
+    "angry",
+    "crazy",
+    "helpful",
+    "mushy",
+    "odd",
+    "unsightly",
+    "adorable",
+    "important",
+    "inexpensive",
+    "cheap",
+    "expensive",
+    "fancy",
+];
+
+static COLOURS: &[&str] = &[
+    "red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
+    "orange",
+];
+
+static NOUNS: &[&str] = &[
+    "table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
+    "pizza", "mouse", "keyboard",
+];

+ 10 - 1
packages/web/examples/hello.rs

@@ -1,3 +1,4 @@
+use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
 use dioxus_web::WebsysRenderer;
 
@@ -10,12 +11,20 @@ fn main() {
 }
 
 static Example: FC<()> = |ctx| {
+    let nodes = (0..15).map(|f| rsx! (li { key: "{f}", "{f}"}));
     ctx.render(rsx! {
         div {
             span {
                 class: "px-2 py-1 flex w-36 mt-4 items-center text-xs rounded-md font-semibold text-yellow-500 bg-yellow-100"
-                "DUE DATE : 18 JUN"
+                "DUE DATE : 189 JUN"
             }
+            p {
+                "these"
+                "are"
+                "text"
+                "nodes"
+            }
+            {nodes}
         }
     })
 };

+ 1 - 0
packages/web/examples/helloworld.rs

@@ -1,3 +1,4 @@
+use dioxus_core as dioxus;
 use dioxus_web::prelude::*;
 
 fn main() {

+ 4 - 5
packages/web/examples/infer.rs

@@ -1,3 +1,4 @@
+use dioxus_core as dioxus;
 use dioxus_core::{events::on::MouseEvent, prelude::*};
 use dioxus_web::WebsysRenderer;
 
@@ -14,7 +15,7 @@ fn main() {
 static Example: FC<()> = |ctx| {
     let (event, set_event) = use_state(&ctx, || None);
 
-    let handler = move |evt: MouseEvent| {
+    let handler = move |evt| {
         set_event(Some(evt));
     };
 
@@ -42,17 +43,15 @@ static Example: FC<()> = |ctx| {
     })
 };
 
-
 #[derive(Debug, PartialEq, Props)]
 struct ExampleProps {
-    name: String
+    name: String,
 }
 
 static Example2: FC<ExampleProps> = |ctx| {
-    ctx.render(rsx!{
+    ctx.render(rsx! {
         div {
             h1 {"hello {ctx.name}"}
         }
     })
 };
-

+ 7 - 14
packages/web/examples/input.rs

@@ -26,25 +26,22 @@ static App: FC<()> = |ctx| {
                         id: "username" 
                         type: "text"
                         value: "{val}"
-                        oninput: move |evet| {
-                            log::debug!("Value is {:#?}", evet);
-                            set_val(evet.value);
-                        }
+                        oninput: move |evet| set_val(evet.value())
                     }
                     p { "Val is: {val}" }
                 }            
             }
         }
-    })    
+    })
 };
 
 static Example: FC<()> = |ctx| {
     ctx.render(rsx! {
         div { class: "max-w-lg max-w-xs bg-blue-800 shadow-2xl rounded-lg mx-auto text-center py-12 mt-4 rounded-xl"
             div { class: "container py-5 max-w-md mx-auto"
-                h1 { class: "text-gray-200 text-center font-extrabold -mt-3 text-3xl", 
+                h1 { class: "text-gray-200 text-center font-extrabold -mt-3 text-3xl",
                     "Text Input Example"
-                } 
+                }
                 UserInput {}
             }
         }
@@ -54,19 +51,15 @@ static Example: FC<()> = |ctx| {
 static UserInput: FC<()> = |ctx| {
     let (val, set_val) = use_state(&ctx, || "asd".to_string());
 
-    rsx!{ in ctx,
+    rsx! { in ctx,
         div { class: "mb-4"
             input { class: "shadow appearance-none rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
                 placeholder: "Username"
-                id: "username" 
+                id: "username"
                 type: "text"
-                oninput: move |evet| {
-                    log::debug!("Value is {:#?}", evet);
-                    set_val(evet.value);
-                }
+                oninput: move |evet| set_val(evet.value())
             }
             p { "Val is: {val}" }
         }
     }
-    
 };

+ 1 - 2
packages/web/examples/jackjill.rs

@@ -33,12 +33,11 @@ static Example: FC<()> = |ctx| {
                             <button
                                 class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
                                 onclick={move |_| set_name("jack")}>
-                                "Jack!"
+                                    "Jack!"
                                 </button>
 
                                 <button
                                     class="inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow"
-                                    onclick={move |_| set_name("jill")}
                                     onclick={move |_| set_name("jill")}>
                                     "Jill!"
                                 </button>

+ 1 - 1
packages/web/examples/landingpage.rs

@@ -6,7 +6,7 @@ use dioxus_core::prelude::*;
 use dioxus_web::*;
 fn main() {
     // Setup logging
-    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    // wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
     console_error_panic_hook::set_once();
     // Run the app
     wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));

+ 1 - 1
packages/web/examples/list.rs

@@ -53,7 +53,7 @@ static App: FC<()> = |ctx| {
                     input {
                         class: "new-todo"
                         placeholder: "What needs to be done?"
-                        oninput: move |evt| set_draft(evt.value)
+                        oninput: move |evt| set_draft(evt.value())
                     }
                 }
 

+ 28 - 0
packages/web/examples/listy.rs

@@ -0,0 +1,28 @@
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_web::WebsysRenderer;
+
+fn main() {
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    console_error_panic_hook::set_once();
+
+    log::info!("hello world");
+    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(JonsFavoriteCustomApp));
+}
+
+fn JonsFavoriteCustomApp(cx: Context<()>) -> VNode {
+    let items = (0..20).map(|f| {
+        rsx! {
+            li {"{f}"}
+        }
+    });
+
+    cx.render(rsx! {
+        div {
+            "list"
+            ul {
+                {items}
+            }
+        }
+    })
+}

+ 1 - 0
packages/web/examples/many.rs

@@ -1,3 +1,4 @@
+use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
 use dioxus_web::WebsysRenderer;
 

+ 13 - 0
packages/web/examples/props.rs

@@ -0,0 +1,13 @@
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+
+#[derive(Props)]
+struct MyProps<'a> {
+    blah: u128,
+    b: &'a (),
+}
+
+fn main() {
+    // let p = unsafe { MyProps {}.memoize(&MyProps {}) };
+    // dbg!(p);
+}

+ 16 - 15
packages/web/examples/rsxt.rs

@@ -1,14 +1,18 @@
 #![allow(non_snake_case)]
-use dioxus_core as dioxus;
+use std::rc::Rc;
+
 use dioxus::{events::on::MouseEvent, prelude::*};
+use dioxus_core as dioxus;
 use dioxus_web::WebsysRenderer;
 
 fn main() {
     wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
     console_error_panic_hook::set_once();
-    
+
     wasm_bindgen_futures::spawn_local(async {
-        let props = ExampleProps { initial_name: "..?"};
+        let props = ExampleProps {
+            initial_name: "..?",
+        };
         WebsysRenderer::new_with_props(Example, props)
             .run()
             .await
@@ -25,17 +29,17 @@ static Example: FC<ExampleProps> = |ctx| {
     let name = use_state_new(&ctx, move || ctx.initial_name);
 
     ctx.render(rsx! {
-        div { 
+        div {
             class: "py-12 px-4 text-center w-full max-w-2xl mx-auto"
-            span { 
+            span {
                 class: "text-sm font-semibold"
                 "Dioxus Example: Jack and Jill"
             }
-            h2 { 
-                class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading"   
+            h2 {
+                class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading"
                 "Hello, {name}"
             }
-            
+
             CustomButton { name: "Jack!", handler: move |_| name.set("Jack") }
             CustomButton { name: "Jill!", handler: move |_| name.set("Jill") }
             CustomButton { name: "Bob!", handler: move |_| name.set("Bob")}
@@ -45,12 +49,10 @@ static Example: FC<ExampleProps> = |ctx| {
     })
 };
 
-
-
 #[derive(Props)]
-struct ButtonProps<'src, F: Fn(MouseEvent)> {
+struct ButtonProps<'src, F: Fn(Rc<dyn MouseEvent>)> {
     name: &'src str,
-    handler: F
+    handler: F,
 }
 
 fn CustomButton<'a, F: Fn(MouseEvent)>(ctx: Context<'a, ButtonProps<'a, F>>) -> VNode {
@@ -69,13 +71,12 @@ impl<F: Fn(MouseEvent)> PartialEq for ButtonProps<'_, F> {
     }
 }
 
-
 #[derive(Props, PartialEq)]
 struct PlaceholderProps {
-    val: &'static str
+    val: &'static str,
 }
 fn Placeholder(ctx: Context<PlaceholderProps>) -> VNode {
-    ctx.render(rsx!{
+    ctx.render(rsx! {
         div {
             "child: {ctx.val}"
         }

+ 8 - 7
packages/web/examples/todomvc/main.rs

@@ -1,10 +1,11 @@
+use dioxus_core as dioxus;
 use dioxus_web::{prelude::*, WebsysRenderer};
 
-mod filtertoggles;
-mod recoil;
-mod state;
-mod todoitem;
-mod todolist;
+// mod filtertoggles;
+// mod recoil;
+// mod state;
+// mod todoitem;
+// mod todolist;
 
 static APP_STYLE: &'static str = include_str!("./style.css");
 
@@ -13,10 +14,10 @@ fn main() {
         ctx.render(rsx! {
             div {
                 id: "app"
-                style { "{APP_STYLE}" }
+                // style { "{APP_STYLE}" }
 
                 // list
-                todolist::TodoList {}
+                // todolist::TodoList {}
 
                 // footer
                 footer {

+ 10 - 12
packages/web/examples/todomvc_simple.rs

@@ -1,5 +1,6 @@
 use std::{collections::HashMap, rc::Rc};
 
+use dioxus_core as dioxus;
 use dioxus_core::prelude::*;
 use dioxus_web::WebsysRenderer;
 
@@ -65,7 +66,7 @@ pub fn TodoList(ctx: Context<()>) -> VNode {
                     class: "new-todo"
                     placeholder: "What needs to be done?"
                     value: "{draft}"
-                    oninput: move |evt| set_draft(evt.value)
+                    oninput: move |evt| set_draft(evt.value())
                 }
             }
 
@@ -78,7 +79,7 @@ pub fn TodoList(ctx: Context<()>) -> VNode {
                     FilterState::Completed => item.checked,
                 })
                 .map(|(id, item)| {
-                    TodoEntry!();
+                    // TodoEntry!();
                     todo!()
                     // rsx!(TodoEntry {
                     //     key: "{order}",
@@ -100,17 +101,14 @@ pub struct TodoEntryProps {
     item: Rc<TodoItem>,
 }
 
-pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> VNode {
-// #[inline_props]
-pub fn TodoEntry(
-    ctx: Context,
-    baller: &impl Fn() -> (),
-    caller: &impl Fn() -> (),
-    todo: &Rc<TodoItem>,
-) -> VNode {
-    // pub fn TodoEntry(ctx: Context, todo: &Rc<TodoItem>) -> VNode {
+pub fn TodoEntry(ctx: Context<TodoEntryProps>) -> VNode {
     let (is_editing, set_is_editing) = use_state(&ctx, || false);
-    // let todo = &ctx.item;
+    let contents = "";
+    let todo = TodoItem {
+        checked: false,
+        contents: "asd".to_string(),
+        id: uuid::Uuid::new_v4(),
+    };
 
     ctx.render(rsx! (
         li {

+ 1 - 1
packages/web/examples/todomvcsingle.rs

@@ -9,7 +9,7 @@
 //! Here, we show to use Dioxus' Recoil state management solution to simplify app logic
 #![allow(non_snake_case)]
 use dioxus_web::dioxus::prelude::*;
-use recoil::*;
+
 use std::collections::HashMap;
 use uuid::Uuid;
 

+ 52 - 51
packages/web/src/lib.rs

@@ -12,7 +12,8 @@ pub use dioxus_core as dioxus;
 use dioxus_core::{events::EventTrigger, prelude::FC};
 
 pub use dioxus_core::prelude;
-pub mod interpreter;
+// pub mod interpreter;
+pub mod new;
 
 /// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM.
 /// Under the hood, we leverage WebSys and interact directly with the DOM
@@ -33,7 +34,7 @@ impl WebsysRenderer {
     ///
     /// Run the app to completion, panicing if any error occurs while rendering.
     /// Pairs well with the wasm_bindgen async handler
-    pub async fn start(root: impl for<'a> Fn(Context<'a, ()>) -> VNode + 'static) {
+    pub async fn start(root: FC<()>) {
         Self::new(root).run().await.expect("Virtual DOM failed :(");
     }
 
@@ -41,7 +42,7 @@ impl WebsysRenderer {
     ///
     /// This means that the root component must either consumes its own context, or statics are used to generate the page.
     /// The root component can access things like routing in its context.
-    pub fn new(root: impl for<'a> Fn(Context<'a, ()>) -> VNode + 'static) -> Self {
+    pub fn new(root: FC<()>) -> Self {
         Self::new_with_props(root, ())
     }
 
@@ -49,10 +50,7 @@ impl WebsysRenderer {
     /// Automatically progresses the creation of the VNode tree to completion.
     ///
     /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
-    pub fn new_with_props<T: Properties + 'static>(
-        root: impl for<'a> Fn(Context<'a, T>) -> VNode + 'static,
-        root_props: T,
-    ) -> Self {
+    pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
         Self::from_vdom(VirtualDom::new_with_props(root, root_props))
     }
 
@@ -62,62 +60,65 @@ impl WebsysRenderer {
     }
 
     pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
-        let (sender, mut receiver) = async_channel::unbounded::<EventTrigger>();
-
         let body_element = prepare_websys_dom();
 
-        let mut patch_machine = interpreter::PatchMachine::new(body_element.clone(), move |ev| {
-            log::debug!("Event trigger! {:#?}", ev);
-            let mut c = sender.clone();
-            wasm_bindgen_futures::spawn_local(async move {
-                c.send(ev).await.unwrap();
-            });
-        });
         let root_node = body_element.first_child().unwrap();
-        patch_machine.stack.push(root_node.clone());
 
-        // todo: initialize the event registry properly on the root
+        let mut websys_dom = crate::new::WebsysDom::new(body_element.clone());
 
-        let edits = self.internal_dom.rebuild()?;
-        log::debug!("Received edits: {:#?}", edits);
-        edits.iter().for_each(|edit| {
-            log::debug!("patching with  {:?}", edit);
-            patch_machine.handle_edit(edit);
-        });
+        websys_dom.stack.push(root_node);
 
-        patch_machine.reset();
-        let root_node = body_element.first_child().unwrap();
-        patch_machine.stack.push(root_node.clone());
+        self.internal_dom.rebuild(&mut websys_dom)?;
+
+        while let Some(trigger) = websys_dom.wait_for_event().await {
+            let root_node = body_element.first_child().unwrap();
+            websys_dom.stack.push(root_node.clone());
+            self.internal_dom
+                .progress_with_event(&mut websys_dom, trigger)?;
+        }
+
+        // let edits = self.internal_dom.rebuild()?;
+        // log::debug!("Received edits: {:#?}", edits);
+        // edits.iter().for_each(|edit| {
+        //     log::debug!("patching with  {:?}", edit);
+        //     patch_machine.handle_edit(edit);
+        // });
+
+        // patch_machine.reset();
+        // let root_node = body_element.first_child().unwrap();
+        // patch_machine.stack.push(root_node.clone());
 
         // log::debug!("patch stack size {:?}", patch_machine.stack);
 
         // Event loop waits for the receiver to finish up
         // TODO! Connect the sender to the virtual dom's suspense system
         // Suspense is basically an external event that can force renders to specific nodes
-        while let Ok(event) = receiver.recv().await {
-            log::debug!("Stack before entrance {:#?}", patch_machine.stack.top());
-            // log::debug!("patch stack size before {:#?}", patch_machine.stack);
-            // patch_machine.reset();
-            // patch_machine.stack.push(root_node.clone());
-            let edits = self.internal_dom.progress_with_event(event)?;
-            log::debug!("Received edits: {:#?}", edits);
-
-            for edit in &edits {
-                // log::debug!("edit stream {:?}", edit);
-                // log::debug!("Stream stack {:#?}", patch_machine.stack.top());
-                patch_machine.handle_edit(edit);
-            }
-
-            // log::debug!("patch stack size after {:#?}", patch_machine.stack);
-            patch_machine.reset();
-            // our root node reference gets invalidated
-            // not sure why
-            // for now, just select the first child again.
-            // eventually, we'll just make our own root element instead of using body
-            // or just use body directly IDEK
-            let root_node = body_element.first_child().unwrap();
-            patch_machine.stack.push(root_node.clone());
-        }
+        // while let Ok(event) = receiver.recv().await {
+        // log::debug!("Stack before entrance {:#?}", patch_machine.stack.top());
+        // log::debug!("patch stack size before {:#?}", patch_machine.stack);
+        // patch_machine.reset();
+        // patch_machine.stack.push(root_node.clone());
+        // self.internal_dom
+        //     .progress_with_event(&mut websys_dom, event)?;
+        // let edits = self.internal_dom.progress_with_event(event)?;
+        // log::debug!("Received edits: {:#?}", edits);
+
+        // for edit in &edits {
+        //     // log::debug!("edit stream {:?}", edit);
+        //     // log::debug!("Stream stack {:#?}", patch_machine.stack.top());
+        //     patch_machine.handle_edit(edit);
+        // }
+
+        // log::debug!("patch stack size after {:#?}", patch_machine.stack);
+        // patch_machine.reset();
+        // our root node reference gets invalidated
+        // not sure why
+        // for now, just select the first child again.
+        // eventually, we'll just make our own root element instead of using body
+        // or just use body directly IDEK
+        // let root_node = body_element.first_child().unwrap();
+        // patch_machine.stack.push(root_node.clone());
+        // }
 
         Ok(()) // should actually never return from this, should be an error, rustc just cant see it
     }

+ 604 - 0
packages/web/src/new.rs

@@ -0,0 +1,604 @@
+use std::{collections::HashMap, rc::Rc, sync::Arc};
+
+use dioxus_core::{
+    events::{EventTrigger, VirtualEvent},
+    prelude::ScopeIdx,
+    virtual_dom::RealDomNode,
+};
+use fxhash::FxHashMap;
+use nohash_hasher::IntMap;
+use wasm_bindgen::{closure::Closure, JsCast};
+use web_sys::{
+    window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
+};
+
+pub struct WebsysDom {
+    pub stack: Stack,
+    nodes: IntMap<u32, Node>,
+    document: Document,
+    root: Element,
+
+    event_receiver: async_channel::Receiver<EventTrigger>,
+    trigger: Arc<dyn Fn(EventTrigger)>,
+
+    // every callback gets a monotomically increasing callback ID
+    callback_id: usize,
+
+    // map of listener types to number of those listeners
+    listeners: FxHashMap<String, (usize, Closure<dyn FnMut(&Event)>)>,
+
+    // Map of callback_id to component index and listener id
+    callback_map: FxHashMap<usize, (usize, usize)>,
+
+    // We need to make sure to add comments between text nodes
+    // We ensure that the text siblings are patched by preventing the browser from merging
+    // neighboring text nodes. Originally inspired by some of React's work from 2016.
+    //  -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
+    //  -> https://github.com/facebook/react/pull/5753
+    //
+    // `ptns` = Percy text node separator
+    // TODO
+    last_node_was_text: bool,
+
+    node_counter: Counter,
+}
+impl WebsysDom {
+    pub fn new(root: Element) -> Self {
+        let document = window()
+            .expect("must have access to the window")
+            .document()
+            .expect("must have access to the Document");
+
+        let (sender, mut receiver) = async_channel::unbounded::<EventTrigger>();
+
+        let sender_callback = Arc::new(move |ev| {
+            let mut c = sender.clone();
+            wasm_bindgen_futures::spawn_local(async move {
+                c.send(ev).await.unwrap();
+            });
+        });
+
+        let mut nodes =
+            HashMap::with_capacity_and_hasher(1000, nohash_hasher::BuildNoHashHasher::default());
+
+        nodes.insert(0_u32, root.clone().dyn_into::<Node>().unwrap());
+        Self {
+            stack: Stack::with_capacity(10),
+            nodes,
+
+            callback_id: 0,
+            listeners: FxHashMap::default(),
+            callback_map: FxHashMap::default(),
+            document,
+            event_receiver: receiver,
+            trigger: sender_callback,
+            root,
+            last_node_was_text: false,
+            node_counter: Counter(0),
+        }
+    }
+
+    pub async fn wait_for_event(&mut self) -> Option<EventTrigger> {
+        let v = self.event_receiver.recv().await.unwrap();
+        Some(v)
+    }
+}
+
+struct Counter(u32);
+impl Counter {
+    fn next(&mut self) -> u32 {
+        self.0 += 1;
+        self.0
+    }
+}
+impl dioxus_core::diff::RealDom for WebsysDom {
+    fn push_root(&mut self, root: dioxus_core::virtual_dom::RealDomNode) {
+        log::debug!("Called `[`push_root] {:?}", root);
+        let domnode = self.nodes.get(&root.0).expect("Failed to pop know root");
+        self.stack.push(domnode.clone());
+    }
+
+    fn append_child(&mut self) {
+        log::debug!("Called [`append_child`]");
+        let child = self.stack.pop();
+
+        if child.dyn_ref::<web_sys::Text>().is_some() {
+            if self.last_node_was_text {
+                let comment_node = self
+                    .document
+                    .create_comment("dioxus")
+                    .dyn_into::<Node>()
+                    .unwrap();
+                self.stack.top().append_child(&comment_node).unwrap();
+            }
+            self.last_node_was_text = true;
+        } else {
+            self.last_node_was_text = false;
+        }
+
+        self.stack.top().append_child(&child).unwrap();
+    }
+
+    fn replace_with(&mut self) {
+        log::debug!("Called [`replace_with`]");
+        let new_node = self.stack.pop();
+        let old_node = self.stack.pop();
+
+        if old_node.has_type::<Element>() {
+            old_node
+                .dyn_ref::<Element>()
+                .unwrap()
+                .replace_with_with_node_1(&new_node)
+                .unwrap();
+        } else if old_node.has_type::<web_sys::CharacterData>() {
+            old_node
+                .dyn_ref::<web_sys::CharacterData>()
+                .unwrap()
+                .replace_with_with_node_1(&new_node)
+                .unwrap();
+        } else if old_node.has_type::<web_sys::DocumentType>() {
+            old_node
+                .dyn_ref::<web_sys::DocumentType>()
+                .unwrap()
+                .replace_with_with_node_1(&new_node)
+                .unwrap();
+        } else {
+            panic!("Cannot replace node: {:?}", old_node);
+        }
+
+        // // poc to see if this is a valid solution
+        // if let Some(id) = self.current_known {
+        //     // update mapping
+        //     self.known_roots.insert(id, new_node.clone());
+        //     self.current_known = None;
+        // }
+
+        self.stack.push(new_node);
+    }
+
+    fn remove(&mut self) {
+        log::debug!("Called [`remove`]");
+        todo!()
+    }
+
+    fn remove_all_children(&mut self) {
+        log::debug!("Called [`remove_all_children`]");
+        todo!()
+    }
+
+    fn create_text_node(&mut self, text: &str) -> dioxus_core::virtual_dom::RealDomNode {
+        let nid = self.node_counter.next();
+        let textnode = self
+            .document
+            .create_text_node(text)
+            .dyn_into::<Node>()
+            .unwrap();
+        self.stack.push(textnode.clone());
+        self.nodes.insert(nid, textnode);
+
+        log::debug!("Called [`create_text_node`]: {}, {}", text, nid);
+
+        RealDomNode::new(nid)
+    }
+
+    fn create_element(&mut self, tag: &str) -> dioxus_core::virtual_dom::RealDomNode {
+        let el = self
+            .document
+            .create_element(tag)
+            .unwrap()
+            .dyn_into::<Node>()
+            .unwrap();
+
+        self.stack.push(el.clone());
+        let nid = self.node_counter.next();
+        self.nodes.insert(nid, el);
+        log::debug!("Called [`create_element`]: {}, {:?}", tag, nid);
+        RealDomNode::new(nid)
+    }
+
+    fn create_element_ns(
+        &mut self,
+        tag: &str,
+        namespace: &str,
+    ) -> dioxus_core::virtual_dom::RealDomNode {
+        let el = self
+            .document
+            .create_element_ns(Some(namespace), tag)
+            .unwrap()
+            .dyn_into::<Node>()
+            .unwrap();
+
+        self.stack.push(el.clone());
+        let nid = self.node_counter.next();
+        self.nodes.insert(nid, el);
+        log::debug!("Called [`create_element_ns`]: {:}", nid);
+        RealDomNode::new(nid)
+    }
+
+    fn new_event_listener(
+        &mut self,
+        event: &str,
+        scope: dioxus_core::prelude::ScopeIdx,
+        el_id: usize,
+        real_id: RealDomNode,
+    ) {
+        log::debug!(
+            "Called [`new_event_listener`]: {}, {:?}, {}, {:?}",
+            event,
+            scope,
+            el_id,
+            real_id
+        );
+        // attach the correct attributes to the element
+        // these will be used by accessing the event's target
+        // This ensures we only ever have one handler attached to the root, but decide
+        // dynamically when we want to call a listener.
+
+        let el = self.stack.top();
+
+        let el = el
+            .dyn_ref::<Element>()
+            .expect(&format!("not an element: {:?}", el));
+
+        let (gi_id, gi_gen) = (&scope).into_raw_parts();
+        el.set_attribute(
+            &format!("dioxus-event-{}", event),
+            &format!("{}.{}.{}.{}", gi_id, gi_gen, el_id, real_id.0),
+        )
+        .unwrap();
+
+        // Register the callback to decode
+
+        if let Some(entry) = self.listeners.get_mut(event) {
+            entry.0 += 1;
+        } else {
+            let trigger = self.trigger.clone();
+            let handler = Closure::wrap(Box::new(move |event: &web_sys::Event| {
+                // "Result" cannot be received from JS
+                // Instead, we just build and immediately execute a closure that returns result
+                let res = || -> anyhow::Result<EventTrigger> {
+                    log::debug!("Handling event!");
+
+                    let target = event
+                        .target()
+                        .expect("missing target")
+                        .dyn_into::<Element>()
+                        .expect("not a valid element");
+
+                    let typ = event.type_();
+                    use anyhow::Context;
+                    let val: String = target
+                        .get_attribute(&format!("dioxus-event-{}", typ))
+                        .context("")?;
+
+                    let mut fields = val.splitn(4, ".");
+
+                    let gi_id = fields
+                        .next()
+                        .and_then(|f| f.parse::<usize>().ok())
+                        .context("")?;
+                    let gi_gen = fields
+                        .next()
+                        .and_then(|f| f.parse::<u64>().ok())
+                        .context("")?;
+                    let el_id = fields
+                        .next()
+                        .and_then(|f| f.parse::<usize>().ok())
+                        .context("")?;
+                    let real_id = fields
+                        .next()
+                        .and_then(|f| f.parse::<u32>().ok().map(RealDomNode::new))
+                        .context("")?;
+
+                    // Call the trigger
+                    log::debug!(
+                        "decoded gi_id: {},  gi_gen: {},  li_idx: {}",
+                        gi_id,
+                        gi_gen,
+                        el_id
+                    );
+
+                    let triggered_scope = ScopeIdx::from_raw_parts(gi_id, gi_gen);
+                    Ok(EventTrigger::new(
+                        virtual_event_from_websys_event(event),
+                        triggered_scope,
+                        real_id,
+                    ))
+                };
+
+                match res() {
+                    Ok(synthetic_event) => trigger.as_ref()(synthetic_event),
+                    Err(_) => log::error!("Error decoding Dioxus event attribute."),
+                };
+            }) as Box<dyn FnMut(&Event)>);
+
+            self.root
+                .add_event_listener_with_callback(event, (&handler).as_ref().unchecked_ref())
+                .unwrap();
+
+            // Increment the listeners
+            self.listeners.insert(event.into(), (1, handler));
+        }
+    }
+
+    fn remove_event_listener(&mut self, event: &str) {
+        log::debug!("Called [`remove_event_listener`]: {}", event);
+        todo!()
+    }
+
+    fn set_text(&mut self, text: &str) {
+        log::debug!("Called [`set_text`]: {}", text);
+        self.stack.top().set_text_content(Some(text))
+    }
+
+    fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) {
+        log::debug!("Called [`set_attribute`]: {}, {}", name, value);
+        if name == "class" {
+            if let Some(el) = self.stack.top().dyn_ref::<Element>() {
+                el.set_class_name(value);
+            }
+        } else {
+            if let Some(el) = self.stack.top().dyn_ref::<Element>() {
+                el.set_attribute(name, value).unwrap();
+            }
+        }
+    }
+
+    fn remove_attribute(&mut self, name: &str) {
+        log::debug!("Called [`remove_attribute`]: {}", name);
+        let node = self.stack.top();
+        if let Some(node) = node.dyn_ref::<web_sys::Element>() {
+            node.remove_attribute(name).unwrap();
+        }
+        if let Some(node) = node.dyn_ref::<HtmlInputElement>() {
+            // Some attributes are "volatile" and don't work through `removeAttribute`.
+            if name == "value" {
+                node.set_value("");
+            }
+            if name == "checked" {
+                node.set_checked(false);
+            }
+        }
+
+        if let Some(node) = node.dyn_ref::<HtmlOptionElement>() {
+            if name == "selected" {
+                node.set_selected(true);
+            }
+        }
+    }
+
+    fn raw_node_as_any_mut(&self) -> &mut dyn std::any::Any {
+        log::debug!("Called [`raw_node_as_any_mut`]");
+        todo!()
+    }
+}
+
+#[derive(Debug, Default)]
+pub struct Stack {
+    list: Vec<Node>,
+}
+
+impl Stack {
+    pub fn with_capacity(cap: usize) -> Self {
+        Stack {
+            list: Vec::with_capacity(cap),
+        }
+    }
+
+    pub fn push(&mut self, node: Node) {
+        // debug!("stack-push: {:?}", node);
+        self.list.push(node);
+    }
+
+    pub fn pop(&mut self) -> Node {
+        let res = self.list.pop().unwrap();
+        res
+    }
+
+    pub fn clear(&mut self) {
+        self.list.clear();
+    }
+
+    pub fn top(&self) -> &Node {
+        match self.list.last() {
+            Some(a) => a,
+            None => panic!("Called 'top' of an empty stack, make sure to push the root first"),
+        }
+    }
+}
+
+fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
+    use dioxus_core::events::on::*;
+    match event.type_().as_str() {
+        "copy" | "cut" | "paste" => {
+            // let evt: web_sys::ClipboardEvent = event.clone().dyn_into().unwrap();
+
+            todo!()
+        }
+
+        "compositionend" | "compositionstart" | "compositionupdate" => {
+            let evt: web_sys::CompositionEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "keydown" | "keypress" | "keyup" => {
+            let evt: web_sys::KeyboardEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "focus" | "blur" => {
+            let evt: web_sys::FocusEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "change" => {
+            let evt: web_sys::Event = event.clone().dyn_into().expect("wrong error typ");
+            todo!()
+            // VirtualEvent::FormEvent(FormEvent {value:})
+        }
+
+        "input" | "invalid" | "reset" | "submit" => {
+            // is a special react events
+            let evt: web_sys::InputEvent = event.clone().dyn_into().expect("wrong event type");
+            let this: web_sys::EventTarget = evt.target().unwrap();
+
+            let value = (&this)
+                .dyn_ref()
+                .map(|input: &web_sys::HtmlInputElement| input.value())
+                .or_else(|| {
+                    (&this)
+                        .dyn_ref()
+                        .map(|input: &web_sys::HtmlTextAreaElement| input.value())
+                })
+                .or_else(|| {
+                    (&this)
+                        .dyn_ref::<web_sys::HtmlElement>()
+                        .unwrap()
+                        .text_content()
+                })
+                .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
+
+            // let p2 = evt.data_transfer();
+
+            // let value: Option<String> = (&evt).data();
+            // let value = val;
+            // let value = value.unwrap_or_default();
+            // let value = (&evt).data().expect("No data to unwrap");
+
+            // todo - this needs to be a "controlled" event
+            // these events won't carry the right data with them
+            todo!()
+            // VirtualEvent::FormEvent(FormEvent { value })
+        }
+
+        "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
+        | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
+        | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
+            let evt: web_sys::MouseEvent = event.clone().dyn_into().unwrap();
+
+            #[derive(Debug)]
+            pub struct CustomMouseEvent(web_sys::MouseEvent);
+            impl dioxus_core::events::on::MouseEvent for CustomMouseEvent {
+                fn alt_key(&self) -> bool {
+                    self.0.alt_key()
+                }
+                fn button(&self) -> i16 {
+                    self.0.button()
+                }
+                fn buttons(&self) -> u16 {
+                    self.0.buttons()
+                }
+                fn client_x(&self) -> i32 {
+                    self.0.client_x()
+                }
+                fn client_y(&self) -> i32 {
+                    self.0.client_y()
+                }
+                fn ctrl_key(&self) -> bool {
+                    self.0.ctrl_key()
+                }
+                fn meta_key(&self) -> bool {
+                    self.0.meta_key()
+                }
+                fn page_x(&self) -> i32 {
+                    self.0.page_x()
+                }
+                fn page_y(&self) -> i32 {
+                    self.0.page_y()
+                }
+                fn screen_x(&self) -> i32 {
+                    self.0.screen_x()
+                }
+                fn screen_y(&self) -> i32 {
+                    self.0.screen_y()
+                }
+                fn shift_key(&self) -> bool {
+                    self.0.shift_key()
+                }
+
+                // yikes
+                // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
+                fn get_modifier_state(&self, key_code: &str) -> bool {
+                    self.0.get_modifier_state(key_code)
+                }
+            }
+            VirtualEvent::MouseEvent(Rc::new(CustomMouseEvent(evt)))
+            // MouseEvent(Box::new(RawMouseEvent {
+            //                 alt_key: evt.alt_key(),
+            //                 button: evt.button() as i32,
+            //                 buttons: evt.buttons() as i32,
+            //                 client_x: evt.client_x(),
+            //                 client_y: evt.client_y(),
+            //                 ctrl_key: evt.ctrl_key(),
+            //                 meta_key: evt.meta_key(),
+            //                 page_x: evt.page_x(),
+            //                 page_y: evt.page_y(),
+            //                 screen_x: evt.screen_x(),
+            //                 screen_y: evt.screen_y(),
+            //                 shift_key: evt.shift_key(),
+            //                 get_modifier_state: GetModifierKey(Box::new(|f| {
+            //                     // evt.get_modifier_state(f)
+            //                     todo!("This is not yet implemented properly, sorry :(");
+            //                 })),
+            //             }))
+            // todo!()
+            // VirtualEvent::MouseEvent()
+        }
+
+        "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
+        | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
+            let evt: web_sys::PointerEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "select" => {
+            // let evt: web_sys::SelectionEvent = event.clone().dyn_into().unwrap();
+            // not required to construct anything special beyond standard event stuff
+            todo!()
+        }
+
+        "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
+            let evt: web_sys::TouchEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "scroll" => {
+            // let evt: web_sys::UIEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "wheel" => {
+            let evt: web_sys::WheelEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
+        | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
+        | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
+        | "timeupdate" | "volumechange" | "waiting" => {
+            // not required to construct anything special beyond standard event stuff
+
+            // let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap();
+            // let evt: web_sys::MediaEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "animationstart" | "animationend" | "animationiteration" => {
+            let evt: web_sys::AnimationEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "transitionend" => {
+            let evt: web_sys::TransitionEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+
+        "toggle" => {
+            // not required to construct anything special beyond standard event stuff (target)
+
+            // let evt: web_sys::ToggleEvent = event.clone().dyn_into().unwrap();
+            todo!()
+        }
+        _ => VirtualEvent::OtherEvent,
+    }
+}

+ 0 - 1
packages/web/src/interpreter.rs → packages/web/src/old/interpreter.rs

@@ -2,7 +2,6 @@ use std::{borrow::Borrow, convert::TryInto, default, fmt::Debug, sync::Arc};
 
 use dioxus_core::{
     events::{EventTrigger, VirtualEvent},
-    patch::Edit,
     prelude::ScopeIdx,
 };
 use fxhash::FxHashMap;

+ 10 - 8
src/lib.rs

@@ -182,12 +182,14 @@ pub mod prelude {
     pub use dioxus_core::prelude::*;
     pub use dioxus_core_macro::fc;
 }
-pub mod builder {
-    // pub use dioxus_core::builder::*;
-}
-pub mod events {
-    // pub use dioxus_core::events::*;
-}
+// pub mod builder {
+//     // pub use dioxus_core::builder::*;
+// }
+pub use dioxus_core::builder;
+pub use dioxus_core::events;
+// pub mod events {
+//     // pub use dioxus_core::events::*;
+// }
 // Just a heads-up, the core functionality of dioxus rests in Dioxus-Core. This crate just wraps a bunch of utilities
 // together and exports their namespaces to something predicatble.
 #[cfg(feature = "core")]
@@ -229,6 +231,6 @@ pub mod atoms {}
 // #[cfg(feature = "desktop")]
 pub mod webview {
     //! A webview based renderer for building desktop applications with Dioxus
-    use dioxus_core::prelude::FC;
-    pub fn launch<P>(f: FC<P>) {}
+    use dioxus_core::prelude::{Properties, FC};
+    pub fn launch<P: Properties>(f: FC<P>) {}
 }