瀏覽代碼

Feat: major overhaul to diffing

Jonathan Kelley 4 年之前
父節點
當前提交
9810feebf5
共有 37 個文件被更改,包括 1463 次插入1122 次删除
  1. 14 5
      Cargo.toml
  2. 7 6
      notes/CHANGELOG.md
  3. 1 1
      packages/cli/Cargo.toml
  4. 202 188
      packages/core-macro/src/rsxt.rs
  5. 4 1
      packages/core-macro/src/util.rs
  6. 1 1
      packages/core/Cargo.toml
  7. 3 3
      packages/core/examples/borrowed.rs
  8. 0 102
      packages/core/examples/dummy.rs
  9. 0 63
      packages/core/examples/fmter.rs
  10. 54 0
      packages/core/examples/macro_testing.rs
  11. 9 3
      packages/core/src/context.rs
  12. 20 13
      packages/core/src/diff.rs
  13. 4 4
      packages/core/src/hooks.rs
  14. 13 35
      packages/core/src/lib.rs
  15. 7 3
      packages/core/src/patch.rs
  16. 0 382
      packages/core/src/scope.rs
  17. 540 143
      packages/core/src/virtual_dom.rs
  18. 119 10
      packages/hooks/src/lib.rs
  19. 12 0
      packages/ios/Cargo.toml
  20. 3 0
      packages/ios/README.md
  21. 70 0
      packages/ios/src/lib.rs
  22. 2 2
      packages/web/Cargo.toml
  23. 1 3
      packages/web/examples/deep.rs
  24. 1 0
      packages/web/examples/demo2.rs
  25. 86 0
      packages/web/examples/demoday.rs
  26. 43 0
      packages/web/examples/landingpage.rs
  27. 43 0
      packages/web/examples/landingpage2.rs
  28. 159 99
      packages/web/examples/list.rs
  29. 7 9
      packages/web/examples/todomvc/filtertoggles.rs
  30. 1 1
      packages/web/examples/todomvc/todoitem.rs
  31. 0 7
      packages/web/examples/todomvc_simple.rs
  32. 1 0
      packages/web/examples/todomvcsingle.rs
  33. 1 2
      packages/web/examples/weather.rs
  34. 8 2
      packages/web/src/interpreter.rs
  35. 1 1
      packages/web/src/lib.rs
  36. 1 1
      packages/webview/Cargo.toml
  37. 25 32
      packages/webview/examples/demo.rs

+ 14 - 5
Cargo.toml

@@ -1,18 +1,28 @@
 [workspace]
+# members = ["packages/core-macro"]
 members = [
-    "packages/dioxus",
     "packages/core-macro",
     "packages/core",
     "packages/web",
-    "packages/docsite",
-    "packages/ssr",
+    "packages/webview",
+    "packages/dioxus",
 ]
+
+# "packages/docsite",
+# "packages/ssr",
+# "packages/cli",
+# "packages/webview",
+# "packages/hooks",
+# "packages/ios",
+
+[profile.dev]
+split-debuginfo = "unpacked"
+
 # "packages/liveview",
 # "packages/3d",
 
 # Built-in
 # "packages/webview/client",
-# "packages/webview",
 # "packages/router",
 # "packages/webview",
 # "packages/livehost",
@@ -22,7 +32,6 @@ members = [
 # "packages/macro",
 # TODO @Jon, share the validation code
 # "packages/web",
-# "packages/hooks",
 # "packages/cli",
 # "examples",
 # "packages/html-macro",

+ 7 - 6
notes/CHANGELOG.md

@@ -24,7 +24,7 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
 > Make it easier to write components
 - [x] (Macro) Tweak event syntax to not be dependent on wasm32 target (just return regular closures which get boxed/alloced)
 - [x] (Macro) Tweak component syntax to accept a new custom element 
-- [ ] (Macro) Allow components to specify their props as function args  (not going to do)
+- [ ] (Macro) Allow components to specify their props as function args
 
 ## Project: Hooks + Context + Subscriptions (TBD)
 > Implement the foundations for state management
@@ -80,20 +80,21 @@ Welcome to the first iteration of the Dioxus Virtual DOM! This release brings su
 - [x] Allow paths for components
 - [x] todo mvc
 - [ ] Make events lazy (use traits + Box<dyn>)
-- [ ] attrs on elements should implement format_args
+- [ ] Attributes on elements should implement format_args
 - [ ] Beef up the dioxus CLI tool
 - [ ] Tweak macro parsing for better errors
-- [ ] make ssr follow HTML spec (ish)
+- [ ] make SSR follow HTML spec
 - [ ] dirty tagging, compression
 - [ ] fix keys on elements
-- [ ] miri tests
+- [ ] MIRI tests
 - [ ] code health
 - [ ] all synthetic events filled out
-- [ ] doublecheck event targets and stuff
+- [ ] double check event targets and stuff
 - [ ] Documentation overhaul
 - [ ] Website
+= [ ] controlled components
 
-lower priorty features
+lower priority features
 - [ ] fragments
 - [ ] node refs (postpone for future release?)
 - [ ] styling built-in (future release?)

+ 1 - 1
packages/cli/Cargo.toml

@@ -12,7 +12,7 @@ description = "CLI tool for developing, testing, and publishing Dioxus apps"
 thiserror = "1.0.23"
 log = "0.4.13"
 fern = { version = "0.6.0", features = ["colored"] }
-wasm-bindgen-cli-support = "0.2.71"
+wasm-bindgen-cli-support = "0.2.73"
 anyhow = "1.0.38"
 argh = "0.1.4"
 serde = "1.0.120"

+ 202 - 188
packages/core-macro/src/rsxt.rs

@@ -1,5 +1,7 @@
 use syn::parse::{discouraged::Speculative, ParseBuffer};
 
+use crate::util::is_valid_html_tag;
+
 use {
     proc_macro::TokenStream,
     proc_macro2::{Span, TokenStream as TokenStream2},
@@ -15,77 +17,105 @@ use {
 // Parse any stream coming from the rsx! macro
 // ==============================================
 pub struct RsxRender {
-    custom_context: Option<Ident>,
-    root: RootOption,
+    // custom_context: Option<Ident>,
+    root: AmbiguousElement,
 }
 
-enum RootOption {
-    // for lists of components
-    Fragment(),
-    Element(Element),
-    Component(Component),
+impl Parse for RsxRender {
+    fn parse(input: ParseStream) -> Result<Self> {
+        let root = { input.parse::<AmbiguousElement>() }?;
+        if !input.is_empty() {
+            return Err(Error::new(
+                input.span(),
+                "Currently only one element is allowed per component",
+            ));
+        }
+
+        Ok(Self { root })
+    }
 }
 
-impl ToTokens for RootOption {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        match self {
-            RootOption::Fragment() => todo!(),
-            RootOption::Element(el) => el.to_tokens(tokens),
-            RootOption::Component(comp) => comp.to_tokens(tokens),
-        }
+impl ToTokens for RsxRender {
+    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
+        // create a lazy tree that accepts a bump allocator
+        // Currently disabled
+        //
+        // let final_tokens = match &self.custom_context {
+        // Some(ident) => quote! {
+        //     #ident.render(dioxus::prelude::LazyNodes::new(move |ctx|{
+        //         let bump = ctx.bump;
+        //         #new_toks
+        //     }))
+        // },
+
+        let inner = &self.root;
+        let output = quote! {
+            dioxus::prelude::LazyNodes::new(move |ctx|{
+                let bump = ctx.bump;
+                #inner
+             })
+        };
+        output.to_tokens(out_tokens)
     }
 }
 
-impl Parse for RsxRender {
+enum AmbiguousElement {
+    Element(Element),
+    Component(Component),
+}
+
+impl Parse for AmbiguousElement {
     fn parse(input: ParseStream) -> Result<Self> {
-        let fork = input.fork();
-
-        let custom_context = fork
-            .parse::<Ident>()
-            .and_then(|ident| {
-                fork.parse::<Token![,]>().map(|_| {
-                    input.advance_to(&fork);
-                    ident
-                })
-            })
-            .ok();
+        // Try to parse as an absolute path and immediately defer to the componetn
+        if input.peek(Token![::]) {
+            return input
+                .parse::<Component>()
+                .map(|c| AmbiguousElement::Component(c));
+        }
 
-        let forked = input.fork();
-        let name = forked.parse::<Ident>()?;
+        // If not an absolute path, then parse the ident and check if it's a valid tag
 
-        let root = match crate::util::is_valid_tag(&name.to_string()) {
-            true => input.parse::<Element>().map(|el| RootOption::Element(el)),
-            false => input.parse::<Component>().map(|c| RootOption::Component(c)),
-        }?;
+        if let Ok(pat) = input.fork().parse::<syn::Path>() {
+            if pat.segments.len() > 1 {
+                return input
+                    .parse::<Component>()
+                    .map(|c| AmbiguousElement::Component(c));
+            }
+        }
 
-        Ok(Self {
-            root,
-            custom_context,
-        })
+        if let Ok(name) = input.fork().parse::<Ident>() {
+            let name_str = name.to_string();
+
+            match is_valid_html_tag(&name_str) {
+                true => input
+                    .parse::<Element>()
+                    .map(|c| AmbiguousElement::Element(c)),
+                false => {
+                    let first_char = name_str.chars().next().unwrap();
+                    if first_char.is_ascii_uppercase() {
+                        input
+                            .parse::<Component>()
+                            .map(|c| AmbiguousElement::Component(c))
+                    } else {
+                        Err(Error::new(
+                            name.span(),
+                            "Components must be uppercased, perhaps you mispelled a html tag",
+                        ))
+                    }
+                }
+            }
+        } else {
+            Err(Error::new(input.span(), "Not a valid Html tag"))
+        }
     }
 }
 
-impl ToTokens for RsxRender {
-    fn to_tokens(&self, out_tokens: &mut TokenStream2) {
-        let new_toks = (&self.root).to_token_stream();
-
-        // create a lazy tree that accepts a bump allocator
-        let final_tokens = match &self.custom_context {
-            Some(ident) => quote! {
-                #ident.render(dioxus::prelude::LazyNodes::new(move |ctx|{
-                    let bump = ctx.bump;
-                    #new_toks
-                }))
-            },
-            None => quote! {
-                dioxus::prelude::LazyNodes::new(move |ctx|{
-                    let bump = ctx.bump;
-                    #new_toks
-                })
-            },
-        };
-
-        final_tokens.to_tokens(out_tokens);
+impl ToTokens for AmbiguousElement {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        match self {
+            AmbiguousElement::Element(el) => el.to_tokens(tokens),
+            AmbiguousElement::Component(comp) => comp.to_tokens(tokens),
+        }
     }
 }
 
@@ -93,9 +123,8 @@ impl ToTokens for RsxRender {
 // Parse any div {} as a VElement
 // ==============================================
 enum Node {
-    Element(Element),
+    Element(AmbiguousElement),
     Text(TextNode),
-    Component(Component),
     RawExpr(Expr),
 }
 
@@ -104,7 +133,6 @@ impl ToTokens for &Node {
         match &self {
             Node::Element(el) => el.to_tokens(tokens),
             Node::Text(txt) => txt.to_tokens(tokens),
-            Node::Component(c) => c.to_tokens(tokens),
             Node::RawExpr(exp) => exp.to_tokens(tokens),
         }
     }
@@ -115,31 +143,17 @@ impl Parse for Node {
         // Supposedly this approach is discouraged due to inability to return proper errors
         // TODO: Rework this to provide more informative errors
 
-        let fork = stream.fork();
-        if let Ok(text) = fork.parse::<TextNode>() {
-            stream.advance_to(&fork);
-            return Ok(Self::Text(text));
-        }
-
-        let fork = stream.fork();
-        if let Ok(element) = fork.parse::<Element>() {
-            stream.advance_to(&fork);
-            return Ok(Self::Element(element));
-        }
-
-        let fork = stream.fork();
-        if let Ok(comp) = fork.parse::<Component>() {
-            stream.advance_to(&fork);
-            return Ok(Self::Component(comp));
+        if stream.peek(token::Brace) {
+            let content;
+            syn::braced!(content in stream);
+            return Ok(Node::RawExpr(content.parse::<Expr>()?));
         }
 
-        let fork = stream.fork();
-        if let Ok(tok) = try_parse_bracketed(&fork) {
-            stream.advance_to(&fork);
-            return Ok(Node::RawExpr(tok));
+        if stream.peek(LitStr) {
+            return Ok(Node::Text(stream.parse::<TextNode>()?));
         }
 
-        return Err(Error::new(stream.span(), "Failed to parse as a valid node"));
+        Ok(Node::Element(stream.parse::<AmbiguousElement>()?))
     }
 }
 
@@ -156,6 +170,7 @@ impl Parse for Component {
         // todo: look into somehow getting the crate/super/etc
 
         let name = syn::Path::parse_mod_style(s)?;
+
         // parse the guts
         let content: ParseBuffer;
         syn::braced!(content in s);
@@ -169,9 +184,7 @@ impl Parse for Component {
                 break 'parsing;
             }
 
-            if let Ok(field) = content.parse::<ComponentField>() {
-                body.push(field);
-            }
+            body.push(content.parse::<ComponentField>()?);
 
             // consume comma if it exists
             // we don't actually care if there *are* commas between attrs
@@ -228,6 +241,7 @@ impl ToTokens for &Component {
         });
     }
 }
+
 // the struct's fields info
 pub struct ComponentField {
     name: Ident,
@@ -258,51 +272,47 @@ impl ToTokens for &ComponentField {
 // =======================================
 struct Element {
     name: Ident,
-    attrs: Vec<Attr>,
+    attrs: Vec<ElementAttr>,
     children: Vec<Node>,
 }
 
-impl ToTokens for &Element {
-    fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let name = &self.name.to_string();
-
-        tokens.append_all(quote! {
-            dioxus::builder::ElementBuilder::new(ctx, #name)
-        });
-
-        for attr in self.attrs.iter() {
-            attr.to_tokens(tokens);
-        }
-
-        let mut children = self.children.iter();
-        while let Some(child) = children.next() {
-            let inner_toks = child.to_token_stream();
-            tokens.append_all(quote! {
-                .iter_child(#inner_toks)
-            })
-        }
-
-        tokens.append_all(quote! {
-            .finish()
-        });
-    }
-}
-
 impl Parse for Element {
-    fn parse(s: ParseStream) -> Result<Self> {
-        let name = Ident::parse_any(s)?;
+    fn parse(stream: ParseStream) -> Result<Self> {
+        //
+        let name = Ident::parse(stream)?;
 
-        if !crate::util::is_valid_tag(&name.to_string()) {
+        if !crate::util::is_valid_html_tag(&name.to_string()) {
             return Err(Error::new(name.span(), "Not a valid Html tag"));
         }
 
         // parse the guts
         let content: ParseBuffer;
-        syn::braced!(content in s);
+        syn::braced!(content in stream);
 
-        let mut attrs: Vec<Attr> = vec![];
+        let mut attrs: Vec<ElementAttr> = vec![];
         let mut children: Vec<Node> = vec![];
-        parse_element_content(content, &mut attrs, &mut children)?;
+        'parsing: loop {
+            // [1] Break if empty
+            if content.is_empty() {
+                break 'parsing;
+            }
+
+            let forked = content.fork();
+            if forked.call(Ident::parse_any).is_ok()
+                && forked.parse::<Token![:]>().is_ok()
+                && forked.parse::<Expr>().is_ok()
+            {
+                attrs.push(content.parse::<ElementAttr>()?);
+            } else {
+                children.push(content.parse::<Node>()?);
+            }
+
+            // consume comma if it exists
+            // we don't actually care if there *are* commas after elements/text
+            if content.peek(Token![,]) {
+                let _ = content.parse::<Token![,]>();
+            }
+        }
 
         Ok(Self {
             name,
@@ -312,62 +322,48 @@ impl Parse for Element {
     }
 }
 
-// used by both vcomponet and velement to parse the contents of the elements into attras and children
-fn parse_element_content(
-    stream: ParseBuffer,
-    attrs: &mut Vec<Attr>,
-    children: &mut Vec<Node>,
-) -> Result<()> {
-    'parsing: loop {
-        // consume comma if it exists
-        // we don't actually care if there *are* commas after elements/text
-        if stream.peek(Token![,]) {
-            let _ = stream.parse::<Token![,]>();
-        }
+impl ToTokens for &Element {
+    fn to_tokens(&self, tokens: &mut TokenStream2) {
+        let name = &self.name.to_string();
 
-        // [1] Break if empty
-        if stream.is_empty() {
-            break 'parsing;
-        }
+        tokens.append_all(quote! {
+            dioxus::builder::ElementBuilder::new(ctx, #name)
+        });
 
-        // [2] Try to consume an attr
-        let fork = stream.fork();
-        if let Ok(attr) = fork.parse::<Attr>() {
-            // make sure to advance or your computer will become a space heater :)
-            stream.advance_to(&fork);
-            attrs.push(attr);
-            continue 'parsing;
+        for attr in self.attrs.iter() {
+            attr.to_tokens(tokens);
         }
 
-        // [3] Try to consume a child node
-        let fork = stream.fork();
-        if let Ok(node) = fork.parse::<Node>() {
-            // make sure to advance or your computer will become a space heater :)
-            stream.advance_to(&fork);
-            children.push(node);
-            continue 'parsing;
+        let mut children = self.children.iter();
+        while let Some(child) = children.next() {
+            let inner_toks = child.to_token_stream();
+            tokens.append_all(quote! {
+                .iter_child(#inner_toks)
+            })
         }
 
-        // todo: pass a descriptive error onto the offending tokens
-        panic!("Entry is not an attr or element\n {}", stream)
+        tokens.append_all(quote! {
+            .finish()
+        });
     }
-    Ok(())
-}
-fn try_parse_bracketed(stream: &ParseBuffer) -> Result<Expr> {
-    let content;
-    syn::braced!(content in stream);
-    content.parse()
 }
 
 /// =======================================
 /// Parse a VElement's Attributes
 /// =======================================
-struct Attr {
+struct ElementAttr {
     name: Ident,
     ty: AttrType,
 }
 
-impl Parse for Attr {
+enum AttrType {
+    Value(LitStr),
+    FieldTokens(Expr),
+    EventTokens(Expr),
+    Event(ExprClosure),
+}
+
+impl Parse for ElementAttr {
     fn parse(s: ParseStream) -> Result<Self> {
         let mut name = Ident::parse_any(s)?;
         let name_str = name.to_string();
@@ -389,32 +385,39 @@ impl Parse for Attr {
                     content.advance_to(&fork);
                     AttrType::Event(event)
                 } else {
-                    AttrType::Tok(content.parse()?)
+                    AttrType::EventTokens(content.parse()?)
                 }
             } else {
                 AttrType::Event(s.parse()?)
             }
         } else {
-            let lit_str = if name_str == "style" && s.peek(token::Brace) {
-                // special-case to deal with literal styles.
-                let outer;
-                syn::braced!(outer in s);
-                // double brace for inline style.
-                // todo!("Style support not ready yet");
-
-                // if outer.peek(token::Brace) {
-                //     let inner;
-                //     syn::braced!(inner in outer);
-                //     let styles: Styles = inner.parse()?;
-                //     MaybeExpr::Literal(LitStr::new(&styles.to_string(), Span::call_site()))
-                // } else {
-                // just parse as an expression
-                outer.parse()?
-            // }
+            let fork = s.fork();
+            if let Ok(rawtext) = fork.parse::<LitStr>() {
+                s.advance_to(&fork);
+                AttrType::Value(rawtext)
             } else {
-                s.parse()?
-            };
-            AttrType::Value(lit_str)
+                let toks = s.parse::<Expr>()?;
+                AttrType::FieldTokens(toks)
+            }
+            // let lit_str = if name_str == "style" && s.peek(token::Brace) {
+            //     // special-case to deal with literal styles.
+            //     let outer;
+            //     syn::braced!(outer in s);
+            //     // double brace for inline style.
+            //     // todo!("Style support not ready yet");
+
+            //     // if outer.peek(token::Brace) {
+            //     //     let inner;
+            //     //     syn::braced!(inner in outer);
+            //     //     let styles: Styles = inner.parse()?;
+            //     //     MaybeExpr::Literal(LitStr::new(&styles.to_string(), Span::call_site()))
+            //     // } else {
+            //     // just parse as an expression
+            //     outer.parse()?
+            // // }
+            // } else {
+            //     s.parse()?
+            // };
         };
 
         // consume comma if it exists
@@ -423,11 +426,11 @@ impl Parse for Attr {
             let _ = s.parse::<Token![,]>();
         }
 
-        Ok(Attr { name, ty })
+        Ok(ElementAttr { name, ty })
     }
 }
 
-impl ToTokens for &Attr {
+impl ToTokens for &ElementAttr {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let name = self.name.to_string();
         let nameident = &self.name;
@@ -435,7 +438,12 @@ impl ToTokens for &Attr {
         match &self.ty {
             AttrType::Value(value) => {
                 tokens.append_all(quote! {
-                    .attr(#name, #value)
+                    .attr(#name, {
+                        use bumpalo::core_alloc::fmt::Write;
+                        let mut s = bumpalo::collections::String::new_in(bump);
+                        s.write_fmt(format_args_f!(#value)).unwrap();
+                        s.into_bump_str()
+                    })
                 });
             }
             AttrType::Event(event) => {
@@ -443,21 +451,21 @@ impl ToTokens for &Attr {
                     .add_listener(dioxus::events::on::#nameident(ctx, #event))
                 });
             }
-            AttrType::Tok(exp) => {
+            AttrType::FieldTokens(exp) => {
                 tokens.append_all(quote! {
-                    .add_listener(dioxus::events::on::#nameident(ctx, #exp))
+                    .attr(#name, #exp)
                 });
             }
+            AttrType::EventTokens(event) => {
+                //
+                tokens.append_all(quote! {
+                    .add_listener(dioxus::events::on::#nameident(ctx, #event))
+                })
+            }
         }
     }
 }
 
-enum AttrType {
-    Value(LitStr),
-    Event(ExprClosure),
-    Tok(Expr),
-}
-
 // =======================================
 // Parse just plain text
 // =======================================
@@ -483,3 +491,9 @@ impl ToTokens for TextNode {
         });
     }
 }
+
+fn try_parse_bracketed(stream: &ParseBuffer) -> Result<Expr> {
+    let content;
+    syn::braced!(content in stream);
+    content.parse()
+}

+ 4 - 1
packages/core-macro/src/util.rs

@@ -117,6 +117,9 @@ static VALID_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
         "var",
         "video",
         "wbr",
+        // SVTG
+        "svg",
+        "path",
     ]
     .iter()
     .cloned()
@@ -132,6 +135,6 @@ static VALID_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
 ///
 /// assert_eq!(is_valid_tag("random"), false);
 /// ```
-pub fn is_valid_tag(tag: &str) -> bool {
+pub fn is_valid_html_tag(tag: &str) -> bool {
     VALID_TAGS.contains(tag)
 }

+ 1 - 1
packages/core/Cargo.toml

@@ -34,4 +34,4 @@ log = "0.4.14"
 serde = { version = "1.0.123", features = ["derive"], optional = true }
 
 [features]
-default = []
+default = ["serde"]

+ 3 - 3
packages/core/examples/borrowed.rs

@@ -7,7 +7,7 @@
 
 fn main() {}
 
-use std::borrow::Borrow;
+use std::{borrow::Borrow, rc::Rc};
 
 use dioxus_core::prelude::*;
 
@@ -36,7 +36,7 @@ fn app<'a>(ctx: Context<'a>, props: &Props) -> DomTree {
                 // create the props with nothing but the fc<T>
                 fc_to_builder(ChildItem)
                     .item(child)
-                    .item_handler(set_val)
+                    .item_handler(set_val.clone())
                     .build(),
                 None,
             ));
@@ -52,7 +52,7 @@ struct ChildProps<'a> {
     item: &'a ListItem,
 
     // Even pass down handlers!
-    item_handler: &'a dyn Fn(i32),
+    item_handler: Rc<dyn Fn(i32)>,
 }
 
 impl PartialEq for ChildProps<'_> {

+ 0 - 102
packages/core/examples/dummy.rs

@@ -1,102 +0,0 @@
-// #![allow(unused, non_upper_case_globals)]
-// use bumpalo::Bump;
-// use dioxus_core::nodebuilder::*;
-// use dioxus_core::prelude::VNode;
-// use dioxus_core::prelude::*;
-// use once_cell::sync::{Lazy, OnceCell};
-
-use std::ops::Deref;
-
-/*
-A guard over underlying T that provides access in callbacks via "Copy"
-*/
-
-// #[derive(Clone)]
-struct ContextGuard2<T> {
-    _val: std::marker::PhantomData<T>,
-}
-impl<T> Clone for ContextGuard2<T> {
-    // we aren't cloning the underlying data so clone isn't necessary
-    fn clone(&self) -> Self {
-        todo!()
-    }
-}
-impl<T> Copy for ContextGuard2<T> {}
-
-impl<T> ContextGuard2<T> {
-    fn get<'a>(&'a self) -> ContextLock<'a, T> {
-        todo!()
-    }
-}
-
-struct ContextLock<'a, T> {
-    _val: std::marker::PhantomData<&'a T>,
-}
-impl<'a, T: 'a + 'static> Deref for ContextLock<'a, T> {
-    type Target = T;
-
-    fn deref<'b>(&'b self) -> &'b T {
-        todo!()
-    }
-}
-
-/*
-The source of the data that gives out context guards
-*/
-struct Context<'a> {
-    _p: std::marker::PhantomData<&'a ()>,
-}
-
-impl<'a> Context<'a> {
-    fn use_context<'b, I, O: 'b>(&self, _f: fn(&'b I) -> O) -> ContextGuard2<O> {
-        todo!()
-    }
-    fn add_listener(&self, _f: impl Fn(()) + 'a) {
-        todo!()
-    }
-
-    fn render(self, _f: impl FnOnce(&'a String) + 'a) {}
-    // fn view(self, f: impl for<'b> FnOnce(&'a String) + 'a) {}
-    // fn view(self, f: impl for<'b> FnOnce(&'b String) + 'a) {}
-}
-
-struct Example {
-    value: String,
-}
-/*
-Example compiling
-*/
-fn t<'a>(ctx: Context<'a>) {
-    let value = ctx.use_context(|b: &Example| &b.value);
-
-    // Works properly, value is moved by copy into the closure
-    let refed = value.get();
-    println!("Value is {}", refed.as_str());
-    let r2 = refed.as_str();
-
-    ctx.add_listener(move |_| {
-        // let val = value.get().as_str();
-        let _val2 = r2.as_bytes();
-        println!("v2 is {}", r2);
-        // println!("refed is {}", refed);
-    });
-
-    // let refed = value.deref();
-    // returns &String
-
-    // returns &String
-    // let refed = value.deref(); // returns &String
-    // let refed = value.deref(); // returns &String
-
-    // Why does this work? This closure should be static but is holding a reference to refed
-    // The context guard is meant to prevent any references moving into the closure
-    // if the references move they might become invalid due to mutlithreading issues
-    ctx.add_listener(move |_| {
-        // let val = value.as_str();
-        // let val2 = refed.as_bytes();
-    });
-
-    ctx.render(move |_b| {});
-}
-
-fn main() {}

+ 0 - 63
packages/core/examples/fmter.rs

@@ -1,63 +0,0 @@
-// #[macro_use]
-
-// use dioxus_core::ifmt;
-// use fstrings::format_args_f;
-
-fn main() {
-    let bump = bumpalo::Bump::new();
-    let _b = &bump;
-    let _world = "123";
-    // dioxus_core::ifmt!(in b; "Hello {world}";);
-}
-
-// let mut s = bumpalo::collections::String::new_in(b);
-// fstrings::write_f!(s, "Hello {world}");
-// dbg!(s);
-// let p = {
-//     println!("hello, {}", &world);
-//     ()
-// };
-// let g = format_args!("hello {world}", world = world);
-// let g = dioxus_core::ifmt!(in b, "Hello {world}");
-// let g = ifmt!(in b, "hhello {world}");
-// let g = ::core::fmt::Arguments::new_v1(
-//     &["hello "],
-//     &match (&world,) {
-//         (arg0,) => [::core::fmt::ArgumentV1::new(
-//             arg0,
-//             ::core::fmt::Display::fmt,
-//         )],
-//     },
-// );
-// fn main() {
-//     let bump = bumpalo::Bump::new();
-//     let b = &bump;
-//     let world = "123";
-//     let world = 123;
-//     let g = {
-//         use bumpalo::core_alloc::fmt::Write;
-//         use ::dioxus_core::prelude::bumpalo;
-//         use ::dioxus_core::prelude::format_args_f;
-//         let bump = b;
-//         let mut s = bumpalo::collections::String::new_in(bump);
-//         let _ = (&mut s).write_fmt(::core::fmt::Arguments::new_v1(
-//             &[""],
-//             &match (&::core::fmt::Arguments::new_v1(
-//                 &["hhello "],
-//                 &match (&world,) {
-//                     (arg0,) => [::core::fmt::ArgumentV1::new(
-//                         arg0,
-//                         ::core::fmt::Display::fmt,
-//                     )],
-//                 },
-//             ),)
-//             {
-//                 (arg0,) => [::core::fmt::ArgumentV1::new(
-//                     arg0,
-//                     ::core::fmt::Display::fmt,
-//                 )],
-//             },
-//         ));
-//         s
-//     };
-// }

+ 54 - 0
packages/core/examples/macro_testing.rs

@@ -0,0 +1,54 @@
+use baller::Baller;
+use dioxus_core::prelude::*;
+
+fn main() {
+    let g = rsx! {
+        div {
+            crate::baller::Baller {}
+            baller::Baller {
+            }
+            Taller {
+                a: "asd"
+            }
+            baller::Baller {}
+            baller::Baller {}
+            Baller {}
+            div {
+                a: "asd",
+                a: "asd",
+                a: "asd",
+                a: "asd",
+                div {
+                    "asdas",
+                    "asdas",
+                    "asdas",
+                    "asdas",
+                    div {
+
+                    },
+                    div {
+
+                    },
+                }
+            }
+        }
+    };
+}
+
+mod baller {
+    use super::*;
+    pub struct BallerProps {}
+
+    pub fn Baller(ctx: Context, props: &()) -> DomTree {
+        todo!()
+    }
+}
+
+#[derive(Debug, PartialEq, Props)]
+pub struct TallerProps {
+    a: &'static str,
+}
+
+pub fn Taller(ctx: Context, props: &TallerProps) -> DomTree {
+    todo!()
+}

+ 9 - 3
packages/core/src/context.rs

@@ -1,4 +1,4 @@
-use crate::{nodebuilder::IntoDomTree, prelude::*, scope::Scope};
+use crate::{innerlude::*, nodebuilder::IntoDomTree};
 use crate::{nodebuilder::LazyNodes, nodes::VNode};
 use bumpalo::Bump;
 use hooks::Hook;
@@ -97,9 +97,12 @@ impl<'a> Context<'a> {
         &self,
         lazy_nodes: LazyNodes<'a, F>,
     ) -> DomTree {
+        // let idx = self.idx.borrow();
         let ctx = NodeCtx {
             bump: &self.scope.cur_frame().bump,
             scope: self.scope.myidx,
+
+            // hmmmmmmmm not sure if this is right
             idx: 0.into(),
             listeners: &self.scope.listeners,
         };
@@ -151,11 +154,14 @@ pub mod hooks {
                 let new_state = initializer();
                 hooks.push(Hook::new(new_state));
             }
-            *self.idx.borrow_mut() = 1;
+
+            *self.idx.borrow_mut() += 1;
 
             let stable_ref = hooks.get_mut(idx).unwrap().0.as_mut();
             let v = unsafe { Pin::get_unchecked_mut(stable_ref) };
-            let internal_state = v.downcast_mut::<InternalHookState>().unwrap();
+            let internal_state = v
+                .downcast_mut::<InternalHookState>()
+                .expect("couldn't find the hook state");
 
             // we extend the lifetime from borrowed in this scope to borrowed from self.
             // This is okay because the hook is pinned

+ 20 - 13
packages/core/src/diff.rs

@@ -32,7 +32,7 @@
 //!
 //! More info on how to improve this diffing algorithm:
 //!  - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
-use crate::{innerlude::*, scope::Scope};
+use crate::innerlude::*;
 use bumpalo::Bump;
 use fxhash::{FxHashMap, FxHashSet};
 use generational_arena::Arena;
@@ -66,26 +66,29 @@ pub struct DiffMachine<'a> {
 pub enum LifeCycleEvent<'a> {
     Mount {
         caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
-        scope: Weak<VCompAssociatedScope>,
-        id: u32,
+        stable_scope_addr: Weak<VCompAssociatedScope>,
+        root_id: u32,
     },
     PropsChanged {
         caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
-        scope: Weak<VCompAssociatedScope>,
-        id: u32,
+        stable_scope_addr: Weak<VCompAssociatedScope>,
+        root_id: u32,
     },
     SameProps {
         caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
-        scope: Weak<VCompAssociatedScope>,
-        id: u32,
+        stable_scope_addr: Weak<VCompAssociatedScope>,
+        root_id: u32,
     },
     Replace {
         caller: Weak<dyn for<'r> Fn(Context<'r>) -> DomTree + 'a>,
         old_scope: Weak<VCompAssociatedScope>,
         new_scope: Weak<VCompAssociatedScope>,
-        id: u32,
+        root_id: u32,
+    },
+    Remove {
+        stable_scope_addr: Weak<VCompAssociatedScope>,
+        root_id: u32,
     },
-    Remove,
 }
 
 static COUNTER: AtomicU32 = AtomicU32::new(1);
@@ -159,7 +162,11 @@ impl<'a> DiffMachine<'a> {
 
                     let scope = Rc::downgrade(&cold.ass_scope);
                     self.lifecycle_events
-                        .push_back(LifeCycleEvent::PropsChanged { caller, id, scope });
+                        .push_back(LifeCycleEvent::PropsChanged {
+                            caller,
+                            root_id: id,
+                            stable_scope_addr: scope,
+                        });
                 } else {
                     let caller = Rc::downgrade(&cnew.caller);
                     let id = cold.stable_addr.borrow().unwrap();
@@ -168,7 +175,7 @@ impl<'a> DiffMachine<'a> {
 
                     self.lifecycle_events.push_back(LifeCycleEvent::Replace {
                         caller,
-                        id,
+                        root_id: id,
                         old_scope,
                         new_scope,
                     });
@@ -262,8 +269,8 @@ impl<'a> DiffMachine<'a> {
                 let scope = Rc::downgrade(&component.ass_scope);
                 self.lifecycle_events.push_back(LifeCycleEvent::Mount {
                     caller: Rc::downgrade(&component.caller),
-                    id,
-                    scope,
+                    root_id: id,
+                    stable_scope_addr: scope,
                 });
             }
             VNode::Suspended => {

+ 4 - 4
packages/core/src/hooks.rs

@@ -21,7 +21,7 @@ mod use_state_def {
     struct UseState<T: 'static> {
         new_val: Rc<RefCell<Option<T>>>,
         current_val: T,
-        caller: Box<dyn Fn(T) + 'static>,
+        caller: Rc<dyn Fn(T) + 'static>,
     }
 
     /// Store state between component renders!
@@ -49,12 +49,12 @@ mod use_state_def {
     pub fn use_state<'a, 'c, T: 'static, F: FnOnce() -> T>(
         ctx: &'c Context<'a>,
         initial_state_fn: F,
-    ) -> (&'a T, &'a impl Fn(T)) {
+    ) -> (&'a T, &'a Rc<dyn Fn(T)>) {
         ctx.use_hook(
             move || UseState {
                 new_val: Rc::new(RefCell::new(None)),
                 current_val: initial_state_fn(),
-                caller: Box::new(|_| println!("setter called!")),
+                caller: Rc::new(|_| println!("setter called!")),
             },
             move |hook| {
                 log::debug!("Use_state set called");
@@ -69,7 +69,7 @@ mod use_state_def {
                 }
 
                 // todo: swap out the caller with a subscription call and an internal update
-                hook.caller = Box::new(move |new_val| {
+                hook.caller = Rc::new(move |new_val| {
                     // update the setter with the new value
                     let mut new_inner = inner.as_ref().borrow_mut();
                     *new_inner = Some(new_val);

+ 13 - 35
packages/core/src/lib.rs

@@ -68,18 +68,14 @@
 pub mod component; // Logic for extending FC
 pub mod context; // Logic for providing hook + context functionality to user components
 pub mod debug_renderer;
-pub mod patch; // An "edit phase" described by transitions and edit operations // Test harness for validating that lifecycles and diffs work appropriately
-               // pub mod diff;
-               // pub mod patch; // The diffing algorithm that builds the ChangeList
 pub mod diff;
-// the diffing algorithm that builds the ChangeList
+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
 pub mod nodebuilder; // Logic for building VNodes with a direct syntax
 pub mod nodes; // Logic for the VNodes
-pub mod scope; // Logic for single components
-               // pub mod validation; //  Logic for validating trees
 pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and suspense
 
 pub mod builder {
@@ -88,37 +84,20 @@ pub mod builder {
 
 // types used internally that are important
 pub(crate) mod innerlude {
-    // pub(crate) use crate::component::Properties;
-    pub(crate) use crate::component::Properties;
-    pub(crate) use crate::context::Context;
-    pub use crate::diff::LifeCycleEvent;
-    pub(crate) use crate::error::Result;
-    pub use crate::events::{EventTrigger, VirtualEvent};
-    use crate::nodes;
-
-    pub use crate::component::ScopeIdx;
-    pub use crate::context::hooks::Hook;
-    pub use crate::nodes::VNode;
-
-    pub(crate) use nodes::*;
-
-    pub use crate::context::NodeCtx;
-    pub use crate::diff::DiffMachine;
-    pub use crate::patch::{EditList, EditMachine};
-    // pub use crate::patchdx;
-    // pub use crate::patchtList;
-    // pub use nodes::iterables::IterableNodes;
-    /// This type alias is an internal way of abstracting over the static functions that represent components.
+    pub use crate::component::*;
+    pub use crate::context::*;
+    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> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
 
-    mod fc2 {}
-    // pub type FC<'a, P: 'a> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
-    // pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'scope P) -> DomTree;
-    // pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'r P) -> VNode<'scope>;
-    // pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'r P) -> VNode<'scope>;
-    // pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
-
     // TODO @Jon, fix this
     // hack the VNode type until VirtualNode is fixed in the macro crate
     pub type VirtualNode<'a> = VNode<'a>;
@@ -127,7 +106,6 @@ pub(crate) mod innerlude {
     pub use crate as dioxus;
     pub use crate::nodebuilder as builder;
     pub use dioxus_core_macro::{html, rsx};
-    // pub use dioxus_core_macro::{fc, html, rsx};
 }
 
 /// Re-export common types for ease of development use.

+ 7 - 3
packages/core/src/patch.rs

@@ -21,9 +21,14 @@
 //! ----
 //! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom)
 
+use std::cell::Ref;
+
 use crate::innerlude::ScopeIdx;
 
 pub type EditList<'src> = Vec<Edit<'src>>;
+// pub struct EditList<'src> {
+//     edits: 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
@@ -146,7 +151,7 @@ pub struct EditMachine<'lock> {
     pub traversal: Traversal,
     next_temporary: u32,
     forcing_new_listeners: bool,
-    
+
     // // if the current node is a "known" node
     // // any actions that modify this node should update the mapping
     // current_known: Option<u32>,
@@ -338,7 +343,7 @@ impl<'a> EditMachine<'a> {
     }
 
     pub fn create_element(&mut self, tag_name: &'a str) {
-        debug_assert!(self.traversal_is_committed());;
+        debug_assert!(self.traversal_is_committed());
         self.emitter.push(Edit::CreateElement { tag_name });
     }
 
@@ -389,7 +394,6 @@ impl<'a> EditMachine<'a> {
         // self.current_known = Some(id);
         self.emitter.push(Edit::TraverseToKnown { node: id })
     }
-
 }
 
 // Keeps track of where we are moving in a DOM tree, and shortens traversal

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

@@ -1,382 +0,0 @@
-use crate::{innerlude::*, virtual_dom::OpaqueComponent};
-use bumpalo::Bump;
-
-use std::{
-    cell::RefCell,
-    collections::HashSet,
-    marker::PhantomData,
-    rc::{Rc, Weak},
-};
-
-/// Every component in Dioxus is represented by a `Scope`.
-///
-/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
-///
-/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
-/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
-pub struct Scope {
-    // Map to the parent
-    pub parent: Option<ScopeIdx>,
-
-    // our own index
-    pub myidx: ScopeIdx,
-
-    //
-    pub children: HashSet<ScopeIdx>,
-
-    pub caller: Weak<OpaqueComponent<'static>>,
-
-    // ==========================
-    // slightly unsafe stuff
-    // ==========================
-    // an internal, highly efficient storage of vnodes
-    pub frames: ActiveFrame,
-
-    // These hooks are actually references into the hook arena
-    // These two could be combined with "OwningRef" to remove unsafe usage
-    // or we could dedicate a tiny bump arena just for them
-    // could also use ourborous
-    pub hooks: RefCell<Vec<Hook>>,
-
-    pub hook_arena: Vec<Hook>,
-
-    // Unsafety:
-    // - 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)>>,
-}
-
-impl Scope {
-    // we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
-    // we are going to break this lifetime by force in order to save it on ourselves.
-    // To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies.
-    // This should never happen, but is a good check to keep around
-    pub fn new<'creator_node>(
-        caller: Weak<OpaqueComponent<'creator_node>>,
-        myidx: ScopeIdx,
-        parent: Option<ScopeIdx>,
-    ) -> Self {
-        // Caller has been broken free
-        // However, it's still weak, so if the original Rc gets killed, we can't touch it
-        let broken_caller: Weak<OpaqueComponent<'static>> = unsafe { std::mem::transmute(caller) };
-
-        Self {
-            caller: broken_caller,
-            hook_arena: Vec::new(),
-            hooks: RefCell::new(Vec::new()),
-            frames: ActiveFrame::new(),
-            children: HashSet::new(),
-            listeners: Default::default(),
-            parent,
-            myidx,
-        }
-    }
-
-    pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
-        let broken_caller: Weak<OpaqueComponent<'static>> = unsafe { std::mem::transmute(caller) };
-
-        self.caller = broken_caller;
-    }
-
-    /// Create a new context and run the component with references from the Virtual Dom
-    /// This function downcasts the function pointer based on the stored props_type
-    ///
-    /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
-    pub fn run_scope<'b>(&'b mut self) -> Result<()> {
-        // cycle to the next frame and then reset it
-        // this breaks any latent references
-        self.frames.next().bump.reset();
-
-        let ctx = Context {
-            idx: 0.into(),
-            _p: PhantomData {},
-            scope: self,
-        };
-
-        let caller = self.caller.upgrade().expect("Failed to get caller");
-
-        /*
-        SAFETY ALERT
-
-        DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
-        KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
-
-        Some things to note:
-        - The VNode itself is bound to the lifetime, but it itself is owned by scope.
-        - The VNode has a private API and can only be used from accessors.
-        - Public API cannot drop or destructure VNode
-        */
-        let new_head = unsafe {
-            // use the same type, just manipulate the lifetime
-            type ComComp<'c> = Rc<OpaqueComponent<'c>>;
-            let caller = std::mem::transmute::<ComComp<'static>, ComComp<'b>>(caller);
-            (caller.as_ref())(ctx)
-        };
-
-        self.frames.cur_frame_mut().head_node = new_head.root;
-        Ok(())
-    }
-
-    // A safe wrapper around calling listeners
-    // calling listeners will invalidate the list of listeners
-    // The listener list will be completely drained because the next frame will write over previous listeners
-    pub fn call_listener(&mut self, trigger: EventTrigger) {
-        let EventTrigger {
-            listener_id,
-            event: source,
-            ..
-        } = trigger;
-
-        unsafe {
-            let listener = self
-                .listeners
-                .borrow()
-                .get(listener_id as usize)
-                .expect("Listener should exist if it was triggered")
-                .as_ref()
-                .unwrap();
-
-            // Run the callback with the user event
-            log::debug!("Running listener");
-            listener(source);
-            log::debug!("Listener finished");
-
-            // drain all the event listeners
-            // if we don't, then they'll stick around and become invalid
-            // big big big big safety issue
-            self.listeners.borrow_mut().drain(..);
-        }
-    }
-
-    pub fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
-        self.frames.current_head_node()
-    }
-
-    pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
-        self.frames.prev_head_node()
-    }
-
-    pub fn cur_frame(&self) -> &BumpFrame {
-        self.frames.cur_frame()
-    }
-}
-
-// ==========================
-// Active-frame related code
-// ==========================
-// todo, do better with the active frame stuff
-// somehow build this vnode with a lifetime tied to self
-// This root node has  "static" lifetime, but it's really not static.
-// It's goverened by the oldest of the two frames and is switched every time a new render occurs
-// Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref
-// ! do not copy this reference are things WILL break !
-pub struct ActiveFrame {
-    pub idx: RefCell<usize>,
-    pub frames: [BumpFrame; 2],
-}
-
-pub struct BumpFrame {
-    pub bump: Bump,
-    pub head_node: VNode<'static>,
-}
-
-impl ActiveFrame {
-    pub fn new() -> Self {
-        Self::from_frames(
-            BumpFrame {
-                bump: Bump::new(),
-                head_node: VNode::text(""),
-            },
-            BumpFrame {
-                bump: Bump::new(),
-                head_node: VNode::text(""),
-            },
-        )
-    }
-
-    fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
-        Self {
-            idx: 0.into(),
-            frames: [a, b],
-        }
-    }
-
-    fn cur_frame(&self) -> &BumpFrame {
-        match *self.idx.borrow() & 1 == 0 {
-            true => &self.frames[0],
-            false => &self.frames[1],
-        }
-    }
-    fn cur_frame_mut(&mut self) -> &mut BumpFrame {
-        match *self.idx.borrow() & 1 == 0 {
-            true => &mut self.frames[0],
-            false => &mut self.frames[1],
-        }
-    }
-
-    pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
-        let raw_node = match *self.idx.borrow() & 1 == 0 {
-            true => &self.frames[0],
-            false => &self.frames[1],
-        };
-
-        // Give out our self-referential item with our own borrowed lifetime
-        unsafe {
-            let unsafe_head = &raw_node.head_node;
-            let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
-            safe_node
-        }
-    }
-
-    pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
-        let raw_node = match *self.idx.borrow() & 1 != 0 {
-            true => &self.frames[0],
-            false => &self.frames[1],
-        };
-
-        // Give out our self-referential item with our own borrowed lifetime
-        unsafe {
-            let unsafe_head = &raw_node.head_node;
-            let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
-            safe_node
-        }
-    }
-
-    fn next(&mut self) -> &mut BumpFrame {
-        *self.idx.borrow_mut() += 1;
-
-        if *self.idx.borrow() % 2 == 0 {
-            &mut self.frames[0]
-        } else {
-            &mut self.frames[1]
-        }
-    }
-}
-
-#[cfg(old)]
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use crate::prelude::*;
-
-    // static ListenerTest: FC<()> = |ctx, props| {
-    //     ctx.render(html! {
-    //         <div onclick={|_| println!("Hell owlrld")}>
-    //             "hello"
-    //         </div>
-    //     })
-    // };
-
-    #[test]
-    fn test_scope() {
-        #[derive(PartialEq)]
-        struct Example {}
-        impl FC for Example {
-            fn render(ctx: Context<'_>, _: &Self) -> DomTree {
-                use crate::builder::*;
-                ctx.render(|ctx| {
-                    builder::ElementBuilder::new(ctx, "div")
-                        .child(text("a"))
-                        .finish()
-                })
-            }
-        }
-
-        let mut nodes = generational_arena::Arena::new();
-        nodes.insert_with(|myidx| {
-            let scope = create_scoped(Example {}, myidx, None);
-        });
-    }
-
-    use crate::{builder::*, hooks::use_ref};
-
-    #[derive(Debug, PartialEq)]
-    struct EmptyProps<'src> {
-        name: &'src String,
-    }
-
-    impl FC for EmptyProps<'_> {
-        fn render(ctx: Context, props: &Self) -> DomTree {
-            let (content, _): (&String, _) = crate::hooks::use_state(&ctx, || "abcd".to_string());
-
-            let childprops: ExampleProps<'_> = ExampleProps { name: content };
-            todo!()
-            // ctx.render(move |c| {
-            //     builder::ElementBuilder::new(c, "div")
-            //         .child(text(props.name))
-            //         .child(virtual_child(c, childprops))
-            //         .finish()
-            // })
-        }
-    }
-
-    #[derive(Debug, PartialEq)]
-    struct ExampleProps<'src> {
-        name: &'src String,
-    }
-
-    impl FC for ExampleProps<'_> {
-        fn render(ctx: Context, props: &Self) -> DomTree {
-            ctx.render(move |ctx| {
-                builder::ElementBuilder::new(ctx, "div")
-                    .child(text(props.name))
-                    .finish()
-            })
-        }
-    }
-
-    #[test]
-    fn test_borrowed_scope() {
-        #[derive(Debug, PartialEq)]
-        struct Example {
-            name: String,
-        }
-
-        impl FC for Example {
-            fn render(ctx: Context, props: &Self) -> DomTree {
-                todo!()
-                // ctx.render(move |c| {
-                //     builder::ElementBuilder::new(c, "div")
-                //         .child(virtual_child(c, ExampleProps { name: &props.name }))
-                //         .finish()
-                // })
-            }
-        }
-
-        let source_text = "abcd123".to_string();
-        let props = ExampleProps { name: &source_text };
-    }
-}
-
-#[cfg(asd)]
-mod old {
-
-    /// The ComponentCaller struct is an opaque object that encapsultes the memoization and running functionality for FC
-    ///
-    /// It's opaque because during the diffing mechanism, the type of props is sealed away in a closure. This makes it so
-    /// scope doesn't need to be generic
-    pub struct ComponentCaller {
-        // used as a memoization strategy
-        comparator: Box<dyn Fn(&Box<dyn Any>) -> bool>,
-
-        // used to actually run the component
-        // encapsulates props
-        runner: Box<dyn Fn(Context) -> DomTree>,
-
-        props_type: TypeId,
-
-        // the actual FC<T>
-        raw: *const (),
-    }
-
-    impl ComponentCaller {
-        fn new<P>(props: P) -> Self {
-            let comparator = Box::new(|f| false);
-            todo!();
-            // Self { comparator }
-        }
-
-        fn update_props<P>(props: P) {}
-    }
-}

+ 540 - 143
packages/core/src/virtual_dom.rs

@@ -1,13 +1,12 @@
-// use crate::{changelist::EditList, nodes::VNode};
-
 use crate::{error::Error, innerlude::*};
-use crate::{patch::Edit, scope::Scope};
+use crate::{innerlude::hooks::Hook, patch::Edit};
+use bumpalo::Bump;
 use generational_arena::Arena;
 use std::{
     any::TypeId,
     borrow::{Borrow, BorrowMut},
-    cell::RefCell,
-    collections::{BTreeMap, BTreeSet},
+    cell::{Ref, RefCell, UnsafeCell},
+    collections::{BTreeMap, BTreeSet, BinaryHeap, HashSet},
     rc::{Rc, Weak},
 };
 use thiserror::private::AsDynError;
@@ -21,23 +20,30 @@ pub(crate) type OpaqueComponent<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + '
 pub struct VirtualDom {
     /// All mounted components are arena allocated to make additions, removals, and references easy to work with
     /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
-    pub components: Arena<Scope>,
+    ///
+    /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
+    /// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
+    components: UnsafeCell<Arena<Scope>>,
 
-    /// The index of the root component.
-    /// Will not be ready if the dom is fresh
+    /// The index of the root component.\
+    /// Should always be the first
     pub base_scope: ScopeIdx,
 
+    /// All components dump their updates into a queue to be processed
     pub(crate) update_schedule: UpdateFunnel,
 
     // a strong allocation to the "caller" for the original props
     #[doc(hidden)]
-    _root_caller: Rc<OpaqueComponent<'static>>,
+    root_caller: Rc<OpaqueComponent<'static>>,
 
     // Type of the original props. This is done so VirtualDom does not need to be generic.
     #[doc(hidden)]
     _root_prop_type: std::any::TypeId,
 }
 
+// ======================================
+// Public Methods for the VirtualDOM
+// ======================================
 impl VirtualDom {
     /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
     ///
@@ -55,109 +61,50 @@ impl VirtualDom {
     pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
         let mut components = Arena::new();
 
-        // let prr = Rc::new(root_props);
-
-        // the root is kept around with a "hard" allocation
-        let root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| {
-            //
-            // let p2 = &root_props;
-            // let p2 = prr.clone();
-            root(ctx, &root_props)
-        });
+        // The user passes in a "root" component (IE the function)
+        // When components are used in the rsx! syntax, the parent assumes ownership
+        // Here, the virtual dom needs to own the function, wrapping it with a `context caller`
+        // The RC holds this component with a hard allocation
+        let root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| root(ctx, &root_props));
 
-        // we then expose this to the component with a weak allocation
-        let weak_caller: Weak<OpaqueComponent> = Rc::downgrade(&root_caller);
-
-        let base_scope = components.insert_with(move |myidx| Scope::new(weak_caller, myidx, None));
+        // To make it easier to pass the root around, we just leak it
+        // When the virtualdom is dropped, we unleak it, so that unsafe isn't here, but it's important to remember
+        let leaked_caller = Rc::downgrade(&root_caller);
 
         Self {
-            components,
-            _root_caller: root_caller,
-            base_scope,
+            root_caller,
+            base_scope: components
+                .insert_with(move |myidx| Scope::new(leaked_caller, myidx, None, 0)),
+            components: UnsafeCell::new(components),
             update_schedule: UpdateFunnel::default(),
             _root_prop_type: TypeId::of::<P>(),
         }
     }
 
-    // consume the top of the diff machine event cycle and dump edits into the edit list
-    pub fn step(&mut self, event: LifeCycleEvent) -> Result<()> {
-        Ok(())
-    }
-
-    /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom.
+    /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom. from scratch
     pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
-        // Diff from the top
-        let mut diff_machine = DiffMachine::new();
-
-        let very_unsafe_components = &mut self.components as *mut generational_arena::Arena<Scope>;
-        let component = self
-            .components
-            .get_mut(self.base_scope)
-            .ok_or_else(|| Error::FatalInternal("Acquring base component should never fail"))?;
-        component.run_scope()?;
-        diff_machine.diff_node(component.old_frame(), component.new_frame());
-
-        // log::debug!("New events generated: {:#?}", diff_machine.lifecycle_events);
-        // chew down the the lifecycle events until all dirty nodes are computed
-        while let Some(event) = diff_machine.lifecycle_events.pop_front() {
-            match event {
-                // A new component has been computed from the diffing algorithm
-                // create a new component in the arena, run it, move the diffing machine to this new spot, and then diff it
-                // this will flood the lifecycle queue with new updates
-                LifeCycleEvent::Mount { caller, id, scope } => {
-                    log::debug!("Mounting a new component");
-
-                    // We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
-                    // those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
-                    // However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
-                    unsafe {
-                        let p = &mut *(very_unsafe_components);
-
-                        // todo, hook up the parent/child indexes properly
-                        let idx = p.insert_with(|f| Scope::new(caller, f, None));
-                        let c = p.get_mut(idx).unwrap();
-
-                        let real_scope = scope.upgrade().unwrap();
-                        *real_scope.as_ref().borrow_mut() = Some(idx);
-                        c.run_scope()?;
-                        diff_machine.change_list.load_known_root(id);
-                        diff_machine.diff_node(c.old_frame(), c.new_frame());
-                    }
-                }
-                LifeCycleEvent::PropsChanged { caller, id, scope } => {
-                    log::debug!("PROPS CHANGED");
-                    let idx = scope.upgrade().unwrap().as_ref().borrow().unwrap();
-                    unsafe {
-                        let p = &mut *(very_unsafe_components);
-                        let c = p.get_mut(idx).unwrap();
-                        c.update_caller(caller);
-                        c.run_scope()?;
-                        diff_machine.change_list.load_known_root(id);
-                        diff_machine.diff_node(c.old_frame(), c.new_frame());
-                    }
-                    // break 'render;
-                }
-                LifeCycleEvent::SameProps { caller, id, scope } => {
-                    log::debug!("SAME PROPS RECEIVED");
-                    //
-                    // break 'render;
-                }
-                LifeCycleEvent::Remove => {
-                    //
-                    // break 'render;
-                }
-                LifeCycleEvent::Replace { caller, id, .. } => {}
-            }
+        todo!()
+    }
+}
 
-            // } else {
-            //     break 'render;
-            // }
-        }
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+struct HeightMarker {
+    idx: ScopeIdx,
+    height: u32,
+}
+impl Ord for HeightMarker {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.height.cmp(&other.height)
+    }
+}
 
-        let edits: Vec<Edit<'s>> = diff_machine.consume();
-        Ok(edits)
+impl PartialOrd for HeightMarker {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
     }
+}
 
+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
@@ -165,7 +112,16 @@ impl VirtualDom {
     /// change list.
     ///
     /// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
-    /// listeners.
+    /// listeners, something like this:
+    ///
+    /// ```ignore
+    /// while let Ok(event) = receiver.recv().await {
+    ///     let edits = self.internal_dom.progress_with_event(event)?;
+    ///     for edit in &edits {
+    ///         patch_machine.handle_edit(edit);
+    ///     }
+    /// }
+    /// ```
     ///
     /// Note: this method is not async and does not provide suspense-like functionality. It is up to the renderer to provide the
     /// executor and handlers for suspense as show in the example.
@@ -182,67 +138,508 @@ impl VirtualDom {
     /// }
     ///
     /// ```
-    pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
-        let component = self
-            .components
-            .get_mut(event.component_id)
-            .expect("Borrowing should not fail");
+    //
+    // Developer notes:
+    // ----
+    // This method has some pretty complex safety guarantees to uphold.
+    // We interact with bump arenas, raw pointers, and use UnsafeCell to get a partial borrow of the arena.
+    // The final EditList has edits that pull directly from the Bump Arenas which add significant complexity
+    // in crafting a 100% safe solution with traditional lifetimes. Consider this method to be internally unsafe
+    // but the guarantees provide a safe, fast, and efficient abstraction for the VirtualDOM updating framework.
+    pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList> {
+        let id = event.component_id.clone();
+
+        unsafe {
+            (&mut *self.components.get())
+                .get_mut(id)
+                .expect("Borrowing should not fail")
+                .call_listener(event)?;
+        }
 
-        component.call_listener(event);
+        // Add this component to the list of components that need to be difed
+        let mut diff_machine = DiffMachine::new();
+        let mut cur_height = 0;
+
+        // Now, there are events in the queue
+        let mut seen_nodes = HashSet::<ScopeIdx>::new();
+        let mut updates = self.update_schedule.0.as_ref().borrow_mut();
+
+        // Order the nodes by their height, we want the biggest nodes on the top
+        // This prevents us from running the same component multiple times
+        updates.sort_unstable();
+
+        // Iterate through the triggered nodes (sorted by height) and begin to diff them
+        for update in updates.drain(..) {
+            // Make sure this isn't a node we've already seen, we don't want to double-render anything
+            // If we double-renderer something, this would cause memory safety issues
+            if seen_nodes.contains(&update.idx) {
+                continue;
+            }
 
-        /*
-        -> call listener
-        -> sort through accumulated queue by the top
-        -> run each component, running its children
-        -> add new updates to the tree of updates (these are dirty)
-        ->
-        */
-        // component.run_scope()?;
+            // Now, all the "seen nodes" are nodes that got notified by running this listener
+            seen_nodes.insert(update.idx.clone());
+
+            unsafe {
+                // Start a new mutable borrow to components
+
+                // We are guaranteeed that this scope is unique because we are tracking which nodes have modified
+                let component = (&mut *self.components.get())
+                    .get_mut(update.idx)
+                    .expect("msg");
+
+                component.run_scope()?;
+
+                diff_machine.diff_node(component.old_frame(), component.next_frame());
+
+                cur_height = component.height + 1;
+            }
+
+            // Now, the entire subtree has been invalidated. We need to descend depth-first and process
+            // any updates that the diff machine has proprogated into the component lifecycle queue
+            while let Some(event) = diff_machine.lifecycle_events.pop_front() {
+                match event {
+                    // A new component has been computed from the diffing algorithm
+                    // create a new component in the arena, run it, move the diffing machine to this new spot, and then diff it
+                    // this will flood the lifecycle queue with new updates to build up the subtree
+                    LifeCycleEvent::Mount {
+                        caller,
+                        root_id: id,
+                        stable_scope_addr,
+                    } => {
+                        log::debug!("Mounting a new component");
+
+                        // We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
+                        // those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
+                        // However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
+                        let components: &mut _ = unsafe { &mut *self.components.get() };
+
+                        // Insert a new scope into our component list
+                        let idx =
+                            components.insert_with(|f| Scope::new(caller, f, None, cur_height));
+
+                        // Grab out that component
+                        let component = components.get_mut(idx).unwrap();
+
+                        // Actually initialize the caller's slot with the right address
+                        *stable_scope_addr.upgrade().unwrap().as_ref().borrow_mut() = Some(idx);
+
+                        // Run the scope for one iteration to initialize it
+                        component.run_scope()?;
+
+                        // Navigate the diff machine to the right point in the output dom
+                        diff_machine.change_list.load_known_root(id);
+
+                        // And then run the diff algorithm
+                        diff_machine.diff_node(component.old_frame(), component.next_frame());
+
+                        // Finally, insert this node as a seen node.
+                        seen_nodes.insert(idx);
+                    }
+
+                    // A component has remained in the same location but its properties have changed
+                    // We need to process this component and then dump the output lifecycle events into the queue
+                    LifeCycleEvent::PropsChanged {
+                        caller,
+                        root_id,
+                        stable_scope_addr,
+                    } => {
+                        log::debug!("Updating a component after its props have changed");
 
-        // let mut diff_machine = DiffMachine::new();
-        // let mut diff_machine = DiffMachine::new(&self.diff_bump);
+                        let components: &mut _ = unsafe { &mut *self.components.get() };
 
-        // diff_machine.diff_node(component.old_frame(), component.new_frame());
+                        // Get the stable index to the target component
+                        // This *should* exist due to guarantees in the diff algorithm
+                        let idx = stable_scope_addr
+                            .upgrade()
+                            .unwrap()
+                            .as_ref()
+                            .borrow()
+                            .unwrap();
 
-        // Ok(diff_machine.consume())
-        self.rebuild()
+                        // Grab out that component
+                        let component = components.get_mut(idx).unwrap();
+
+                        // We have to move the caller over or running the scope will fail
+                        component.update_caller(caller);
+
+                        // Run the scope
+                        component.run_scope()?;
+
+                        // Navigate the diff machine to the right point in the output dom
+                        diff_machine.change_list.load_known_root(root_id);
+
+                        // And then run the diff algorithm
+                        diff_machine.diff_node(component.old_frame(), component.next_frame());
+
+                        // Finally, insert this node as a seen node.
+                        seen_nodes.insert(idx);
+                    }
+
+                    // A component's parent has updated, but its properties did not change.
+                    // This means the caller ptr is invalidated and needs to be updated, but the component itself does not need to be re-ran
+                    LifeCycleEvent::SameProps {
+                        caller,
+                        root_id,
+                        stable_scope_addr,
+                    } => {
+                        // In this case, the parent made a new DomTree that resulted in the same props for us
+                        // However, since our caller is located in a Bump frame, we need to update the caller pointer (which is now invalid)
+                        log::debug!("Received the same props");
+
+                        let components: &mut _ = unsafe { &mut *self.components.get() };
+
+                        // Get the stable index to the target component
+                        // This *should* exist due to guarantees in the diff algorithm
+                        let idx = stable_scope_addr
+                            .upgrade()
+                            .unwrap()
+                            .as_ref()
+                            .borrow()
+                            .unwrap();
+
+                        // Grab out that component
+                        let component = components.get_mut(idx).unwrap();
+
+                        // We have to move the caller over or running the scope will fail
+                        component.update_caller(caller);
+
+                        // This time, we will not add it to our seen nodes since we did not actually run it
+                    }
+
+                    LifeCycleEvent::Remove {
+                        root_id,
+                        stable_scope_addr,
+                    } => {
+                        unimplemented!("This feature (Remove) is unimplemented")
+                    }
+                    LifeCycleEvent::Replace {
+                        caller,
+                        root_id: id,
+                        ..
+                    } => {
+                        unimplemented!("This feature (Replace) is unimplemented")
+                    }
+                }
+            }
+        }
+
+        Ok(diff_machine.consume())
     }
 }
 
-#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
-pub struct HierarchyMarker {
-    source: ScopeIdx,
+impl Drop for VirtualDom {
+    fn drop(&mut self) {
+        // Drop all the components first
+        // self.components.drain();
+
+        // Finally, drop the root caller
+        unsafe {
+            // let root: Box<OpaqueComponent<'static>> =
+            //     Box::from_raw(self.root_caller as *const OpaqueComponent<'static> as *mut _);
+
+            // std::mem::drop(root);
+        }
+    }
 }
 
-#[derive(Debug, Default, PartialEq, Clone)]
-pub struct UpdateFunnel(Rc<RefCell<Vec<HierarchyMarker>>>);
+#[derive(Debug, Default, Clone)]
+pub struct UpdateFunnel(Rc<RefCell<Vec<HeightMarker>>>);
 
 impl UpdateFunnel {
-    fn schedule_update(&self, source: ScopeIdx) -> impl Fn() {
+    fn schedule_update(&self, source: &Scope) -> impl Fn() {
         let inner = self.clone();
-        move || {
-            inner
-                .0
+        let marker = HeightMarker {
+            height: source.height,
+            idx: source.myidx,
+        };
+        move || inner.0.as_ref().borrow_mut().push(marker)
+    }
+}
+/// Every component in Dioxus is represented by a `Scope`.
+///
+/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
+///
+/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
+/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
+pub struct Scope {
+    // The parent's scope ID
+    pub parent: Option<ScopeIdx>,
+
+    // Our own ID accessible from the component map
+    pub myidx: ScopeIdx,
+
+    pub height: u32,
+
+    // A list of children
+    // TODO, repalce the hashset with a faster hash function
+    pub children: HashSet<ScopeIdx>,
+
+    // caller: &'static OpaqueComponent<'static>,
+    pub caller: Weak<OpaqueComponent<'static>>,
+
+    // ==========================
+    // slightly unsafe stuff
+    // ==========================
+    // an internal, highly efficient storage of vnodes
+    pub(crate) frames: ActiveFrame,
+
+    // These hooks are actually references into the hook arena
+    // These two could be combined with "OwningRef" to remove unsafe usage
+    // or we could dedicate a tiny bump arena just for them
+    // could also use ourborous
+    pub(crate) hooks: RefCell<Vec<Hook>>,
+
+    // Unsafety:
+    // - 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)>>,
+}
+
+impl Scope {
+    // we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
+    // we are going to break this lifetime by force in order to save it on ourselves.
+    // To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies.
+    // This should never happen, but is a good check to keep around
+    //
+    // Scopes cannot be made anywhere else except for this file
+    // Therefore, their lifetimes are connected exclusively to the virtual dom
+    fn new<'creator_node>(
+        caller: Weak<OpaqueComponent<'creator_node>>,
+        myidx: ScopeIdx,
+        parent: Option<ScopeIdx>,
+        height: u32,
+    ) -> Self {
+        log::debug!(
+            "New scope created, height is {}, idx is {:?}",
+            height,
+            myidx
+        );
+        // Caller has been broken free
+        // However, it's still weak, so if the original Rc gets killed, we can't touch it
+        let broken_caller: Weak<OpaqueComponent<'static>> = unsafe { std::mem::transmute(caller) };
+
+        Self {
+            caller: broken_caller,
+            hooks: RefCell::new(Vec::new()),
+            frames: ActiveFrame::new(),
+            children: HashSet::new(),
+            listeners: Default::default(),
+            parent,
+            myidx,
+            height,
+        }
+    }
+    pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
+        let broken_caller: Weak<OpaqueComponent<'static>> = unsafe { std::mem::transmute(caller) };
+
+        self.caller = broken_caller;
+    }
+
+    /// Create a new context and run the component with references from the Virtual Dom
+    /// This function downcasts the function pointer based on the stored props_type
+    ///
+    /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
+    pub fn run_scope<'b>(&'b mut self) -> Result<()> {
+        // cycle to the next frame and then reset it
+        // this breaks any latent references
+        self.frames.next().bump.reset();
+
+        let ctx = Context {
+            idx: 0.into(),
+            _p: std::marker::PhantomData {},
+            scope: self,
+        };
+
+        let caller = self.caller.upgrade().expect("Failed to get caller");
+
+        /*
+        SAFETY ALERT
+
+        DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
+        KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
+
+        Some things to note:
+        - The VNode itself is bound to the lifetime, but it itself is owned by scope.
+        - The VNode has a private API and can only be used from accessors.
+        - Public API cannot drop or destructure VNode
+        */
+        let new_head = unsafe {
+            // use the same type, just manipulate the lifetime
+            type ComComp<'c> = Rc<OpaqueComponent<'c>>;
+            let caller = std::mem::transmute::<ComComp<'static>, ComComp<'b>>(caller);
+            (caller.as_ref())(ctx)
+        };
+
+        self.frames.cur_frame_mut().head_node = new_head.root;
+        Ok(())
+    }
+
+    // A safe wrapper around calling listeners
+    // calling listeners will invalidate the list of listeners
+    // 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: source,
+            ..
+        } = trigger;
+
+        unsafe {
+            // Convert the raw ptr into an actual object
+            // This operation is assumed to be safe
+
+            log::debug!("Running listener");
+
+            self.listeners
+                .borrow()
+                .get(listener_id as usize)
+                .ok_or(Error::FatalInternal("Event should exist if it was triggered"))?
                 .as_ref()
-                .borrow_mut()
-                .push(HierarchyMarker { source })
+                .ok_or(Error::FatalInternal("Raw event ptr is invalid"))?
+                // Run the callback with the user event
+                (source);
+
+            log::debug!("Listener finished");
+
+            // drain all the event listeners
+            // if we don't, then they'll stick around and become invalid
+            // big big big big safety issue
+            self.listeners.borrow_mut().drain(..);
         }
+        Ok(())
+    }
+
+    pub fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
+        self.frames.current_head_node()
+    }
+
+    pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
+        self.frames.prev_head_node()
     }
+
+    pub fn cur_frame(&self) -> &BumpFrame {
+        self.frames.cur_frame()
+    }
+}
+
+// ==========================
+// Active-frame related code
+// ==========================
+// todo, do better with the active frame stuff
+// somehow build this vnode with a lifetime tied to self
+// This root node has  "static" lifetime, but it's really not static.
+// It's goverened by the oldest of the two frames and is switched every time a new render occurs
+// Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref
+// ! do not copy this reference are things WILL break !
+pub struct ActiveFrame {
+    pub idx: RefCell<usize>,
+    pub frames: [BumpFrame; 2],
+
+    // We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
+    pub generation: u32,
+}
+
+pub struct BumpFrame {
+    pub bump: Bump,
+    pub head_node: VNode<'static>,
 }
 
-macro_rules! UpdateFunnel {
-    (root: $root:expr) => {
-        VirtualDom::new($root)
-    };
+impl ActiveFrame {
+    pub fn new() -> Self {
+        Self::from_frames(
+            BumpFrame {
+                bump: Bump::new(),
+                head_node: VNode::text(""),
+            },
+            BumpFrame {
+                bump: Bump::new(),
+                head_node: VNode::text(""),
+            },
+        )
+    }
+
+    fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
+        Self {
+            idx: 0.into(),
+            frames: [a, b],
+            generation: 0,
+        }
+    }
+
+    fn cur_frame(&self) -> &BumpFrame {
+        match *self.idx.borrow() & 1 == 0 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        }
+    }
+    fn cur_frame_mut(&mut self) -> &mut BumpFrame {
+        match *self.idx.borrow() & 1 == 0 {
+            true => &mut self.frames[0],
+            false => &mut self.frames[1],
+        }
+    }
+
+    pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
+        let raw_node = match *self.idx.borrow() & 1 == 0 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        };
+
+        // Give out our self-referential item with our own borrowed lifetime
+        unsafe {
+            let unsafe_head = &raw_node.head_node;
+            let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
+            safe_node
+        }
+    }
+
+    pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
+        let raw_node = match *self.idx.borrow() & 1 != 0 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        };
+
+        // Give out our self-referential item with our own borrowed lifetime
+        unsafe {
+            let unsafe_head = &raw_node.head_node;
+            let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
+            safe_node
+        }
+    }
+
+    fn next(&mut self) -> &mut BumpFrame {
+        *self.idx.borrow_mut() += 1;
+
+        if *self.idx.borrow() % 2 == 0 {
+            &mut self.frames[0]
+        } else {
+            &mut self.frames[1]
+        }
+    }
 }
 
-// #[test]
-// fn test_new_vdom() {
-//     let dom = UpdateFunnel! {
-//         root: |ctx, props| {
-//             ctx.render(rsx!{
+mod test {
+    use super::*;
+
+    #[test]
+    fn simulate() {
+        let dom = VirtualDom::new(|ctx, props| {
+            //
+            ctx.render(rsx! {
+                div {
+
+                }
+            })
+        });
+        // let root = dom.components.get(dom.base_scope).unwrap();
+    }
 
-//             })
-//         }
-//     };
-// }
+    // // This marker is designed to ensure resources shared from one bump to another are handled properly
+    // // The underlying T may be already freed if there is an issue with our crate
+    // pub(crate) struct BumpResource<T: 'static> {
+    //     resource: T,
+    //     scope: ScopeIdx,
+    //     gen: u32,
+    // }
+}

+ 119 - 10
packages/hooks/src/lib.rs

@@ -1,10 +1,119 @@
-// mod hooks;
-// pub use hooks::use_context;
-
-// pub mod prelude {
-//     use dioxus_core::prelude::Context;
-//     pub fn use_state<T, G>(ctx: &Context<G>, init: impl Fn() -> T) -> (T, impl Fn(T)) {
-//         let g = init();
-//         (g, |_| {})
-//     }
-// }
+use std::collections::HashMap;
+
+use dioxus_core::prelude::*;
+
+/*
+a form of use_state explicitly for map-style collections (BTreeMap, HashMap, etc).
+
+Why?
+---
+Traditionally, it's possible to use the "use_state" hook for collections in the React world.
+Adding a new entry would look something similar to:
+
+```js
+let (map, set_map) = useState({});
+set_map({ ...map, [key]: value });
+```
+The new value then causes the appropriate update when passed into children.
+
+This is moderately efficient because the fields of the map are moved, but the data itself is not cloned.
+However, if you used similar approach with Dioxus:
+
+```rust
+let (map, set_map) = use_state(ctx, || HashMap::new());
+set_map({
+    let mut newmap = map.clone();
+    newmap.set(key, value);
+    newmap
+})
+```
+Unfortunately, you'd be cloning the entire state every time a value is changed. The obvious solution is to
+wrap every element in the HashMap with an Rc. That way, cloning the HashMap is on par with its JS equivalent.
+
+Fortunately, we can make this operation even more efficient in Dioxus, leveraging the borrow rules of Rust.
+
+This hook provides a memoized collection, memoized setters, and memoized getters. This particular hook is
+extremely powerful for implementing lists and supporting core state management needs for small apps.
+
+If you need something even more powerful, check out the dedicated atomic state management Dioxus Dataflow, which
+uses the same memoization on top of the use_context API.
+
+Here's a fully-functional todo app using the use_map API:
+```rust
+static TodoList: FC<()> = |ctx, props| {
+    let todos = use_map(ctx, || HashMap::new());
+    let input = use_ref(|| None);
+
+    ctx.render(rsx!{
+        div {
+            button {
+                "Add todo"
+                onclick: move |_| {
+                    let new_todo = TodoItem::new(input.contents());
+                    todos.insert(new_todo.id.clone(), new_todo);
+                    input.clear();
+                }
+            }
+            button {
+                "Clear todos"
+                onclick: move |_| todos.clear()
+            }
+            input {
+                placeholder: "What needs to be done?"
+                ref: input
+            }
+            ul {
+                {todos.iter().map(|todo| rsx!(
+                    li {
+                        key: todo.id
+                        span { "{todo.content}" }
+                        button {"x", onclick: move |_| todos.remove(todo.key.clone())}
+                    }
+                ))}
+            }
+        }
+    })
+}
+
+```
+
+*/
+fn use_map() {}
+
+// a form of "use_state" that allows collection memoization
+// Elements are received as Rc<T> in case the underlying collection is shuffled around
+// Setters/getters can be generated
+fn use_collection<'a, T: Collection>(
+    ctx: &Context<'a>,
+    f: impl Fn() -> T,
+) -> CollectionHandle<'a, T> {
+    ctx.use_hook(
+        || {},
+        |h| {
+            //
+            CollectionHandle {
+                _p: Default::default(),
+            }
+        },
+        |h| {},
+    )
+}
+
+struct CollectionMemo {}
+
+struct CollectionHandle<'a, T: Collection> {
+    _p: std::marker::PhantomData<&'a T>,
+}
+
+trait Collection {}
+impl<K, V> Collection for std::collections::HashMap<K, V> {}
+
+struct MapCollection<K, V> {
+    inner: HashMap<K, V>,
+}
+
+impl<K, V> MapCollection<K, V> {
+    fn set(&self, key: K, val: V) {
+        //
+    }
+}

+ 12 - 0
packages/ios/Cargo.toml

@@ -0,0 +1,12 @@
+[package]
+name = "dioxus-ios"
+version = "0.0.0"
+authors = ["Jonathan Kelley <jkelleyrtp@gmail.com>"]
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+cacao = { git = "https://github.com/ryanmcgrath/cacao" }
+dioxus-core = { path = "../core", version = "0.1.2" }
+log = "0.4.14"

+ 3 - 0
packages/ios/README.md

@@ -0,0 +1,3 @@
+# Dioxus for iOS
+
+This crate implements a renderer of the Dioxus DomTree to an iOS app 

+ 70 - 0
packages/ios/src/lib.rs

@@ -0,0 +1,70 @@
+use dioxus::virtual_dom::VirtualDom;
+pub use dioxus_core as dioxus;
+use dioxus_core::{events::EventTrigger, prelude::FC};
+
+pub struct IosRenderer {
+    internal_dom: VirtualDom,
+}
+
+impl IosRenderer {
+    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 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());
+        }
+
+        Ok(()) // should actually never return from this, should be an error, rustc just cant see it
+    }
+}

+ 2 - 2
packages/web/Cargo.toml

@@ -29,7 +29,7 @@ async-channel = "1.6.1"
 # futures-lite = "1.11.3"
 
 [dependencies.web-sys]
-version = "0.3"
+version = "0.3.50"
 features = [
     "Comment",
     "Document",
@@ -71,7 +71,7 @@ crate-type = ["cdylib", "rlib"]
 
 
 [dev-dependencies]
-uuid = { version = "0.8.2", features = ["v4"] }
+uuid = { version = "0.8.2", features = ["v4", "wasm-bindgen"] }
 
 [[example]]
 name = "todomvc"

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

@@ -7,8 +7,6 @@ fn main() {
     wasm_bindgen_futures::spawn_local(WebsysRenderer::start(CustomA))
 }
 
-use components::CustomB;
-
 fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
     let (val, set_val) = use_state(&ctx, || "abcdef".to_string());
 
@@ -24,7 +22,7 @@ fn CustomA<'a>(ctx: Context<'a>, props: &'a ()) -> DomTree {
                 "Lower"
                 onclick: move |_| set_val(val.to_ascii_lowercase())
             }
-            CustomB {
+            components::CustomB {
                 val: val.as_ref()
             }
         }

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

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

+ 86 - 0
packages/web/examples/demoday.rs

@@ -0,0 +1,86 @@
+use dioxus_web::{dioxus::prelude::*, 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))
+}
+
+fn App(ctx: Context, props: &()) -> DomTree {
+    ctx.render(rsx! {
+        main { class: "dark:bg-gray-800 bg-white relative h-screen"
+            NavBar {}
+            {(0..10).map(|f| rsx!{ Landing {} })}
+        }
+    })
+}
+
+fn NavBar(ctx: Context, props: &()) -> DomTree {
+    ctx.render(rsx!{
+        header { class: "h-24 sm:h-32 flex items-center z-30 w-full"
+            div { class: "container mx-auto px-6 flex items-center justify-between"
+                div { class: "uppercase text-gray-800 dark:text-white font-black text-3xl"
+                    svg { focusable:"false" width:"100" height:"100" viewBox: "0 0 512 309"
+                        path { fill: "#000"
+                            d: "M120.81 80.561h96.568v7.676h-87.716v57.767h82.486v7.675h-82.486v63.423h88.722v7.675H120.81V80.561zm105.22 0h10.26l45.467 63.423L328.23 80.56L391.441 0l-103.85 150.65l53.515 74.127h-10.663l-48.686-67.462l-48.888 67.462h-10.461l53.917-74.128l-50.296-70.088zm118.898 7.676V80.56h110.048v7.676h-50.699v136.54h-8.852V88.237h-50.497zM0 80.56h11.065l152.58 228.323l-63.053-84.107L9.254 91.468l-.402 133.31H0V80.56zm454.084 134.224c-1.809 0-3.165-1.4-3.165-3.212c0-1.81 1.356-3.212 3.165-3.212c1.83 0 3.165 1.401 3.165 3.212c0 1.811-1.335 3.212-3.165 3.212zm8.698-8.45h4.737c.064 2.565 1.937 4.29 4.693 4.29c3.079 0 4.823-1.854 4.823-5.325v-21.99h4.823v22.011c0 6.252-3.617 9.853-9.603 9.853c-5.62 0-9.473-3.493-9.473-8.84zm25.384-.28h4.78c.409 2.953 3.294 4.828 7.45 4.828c3.875 0 6.717-2.005 6.717-4.764c0-2.371-1.809-3.794-5.921-4.764l-4.005-.97c-5.62-1.316-8.181-4.032-8.181-8.602c0-5.54 4.521-9.227 11.303-9.227c6.308 0 10.916 3.686 11.196 8.925h-4.694c-.452-2.867-2.95-4.657-6.567-4.657c-3.81 0-6.35 1.833-6.35 4.635c0 2.22 1.635 3.493 5.683 4.441l3.423.841c6.373 1.488 9 4.075 9 8.753c0 5.95-4.607 9.68-11.97 9.68c-6.89 0-11.52-3.558-11.864-9.12z" 
+                        }
+                    }
+                }
+                div { class:"flex items-center"
+                    nav { class: "font-sen text-gray-800 dark:text-white uppercase text-lg lg:flex items-center hidden"
+                        a { href: "#", class:"py-2 px-6 flex text-indigo-500 border-b-2 border-indigo-500"
+                            "Home"
+                        }
+                        a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
+                            "Watch"
+                        }
+                        a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
+                            "Product"
+                        }
+                        a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
+                            "Contact"
+                        }
+                        a { href: "#", class: "py-2 px-6 flex hover:text-indigo-500"
+                            "Career"
+                        }
+                    }
+                    button { class: "lg:hidden flex flex-col ml-4"
+                        span { class: "w-6 h-1 bg-gray-800 dark:bg-white mb-1" }
+                        span { class: "w-6 h-1 bg-gray-800 dark:bg-white mb-1" }
+                        span { class: "w-6 h-1 bg-gray-800 dark:bg-white mb-1" }
+                    }
+                }
+            }
+        }
+    })
+}
+
+fn Landing(ctx: Context, props: &()) -> DomTree {
+    ctx.render(rsx!{
+        div { class: "bg-white dark:bg-gray-800 flex relative z-20 items-center"
+            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"
+                    }
+                    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
+                        hybrid static &amp; server rendering, TypeScript support, smart bundling, route pre-fetching, and \n
+                        more. No config needed."
+                    }
+                    div { class: "flex items-center justify-center mt-4"
+                        a { href: "#" class: "uppercase py-2 px-4 bg-gray-800 border-2 border-transparent text-white text-md mr-4 hover:bg-gray-900"
+                            "Get started"
+                        }
+                        a{ href: "#" class: "uppercase py-2 px-4 bg-transparent border-2 border-gray-800 text-gray-800 dark:text-white hover:bg-gray-800 hover:text-white text-md"
+                            "Documentation"
+                        }
+                    }
+                }
+                div { class: "block w-full mx-auto mt-6 md:mt-0 relative"
+                    img { src: "/images/object/12.svg" class: "max-w-xs md:max-w-2xl m-auto" }
+                }
+            }
+        }
+    })
+}

+ 43 - 0
packages/web/examples/landingpage.rs

@@ -0,0 +1,43 @@
+//! Basic example that renders a simple domtree to the browser.
+
+use std::rc::Rc;
+
+use dioxus_core::prelude::*;
+use dioxus_web::*;
+fn main() {
+    // Setup logging
+    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));
+}
+
+static App: FC<()> = |ctx, _| {
+    let (contents, set_contents) = use_state(&ctx, || "asd");
+
+    ctx.render(rsx! {
+        div  {
+            class: "flex items-center justify-center flex-col"
+            div {
+                class: "flex items-center justify-center"
+                div {
+                    class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
+                    div { class: "font-bold text-xl", "Example cloud app" }
+                    div { class: "text-sm text-gray-500", "This is running in the cloud!!" }
+                    div {
+                        class: "flex flex-row items-center justify-center mt-6"
+                        div { class: "font-medium text-6xl", "100%" }
+                    }
+                    div {
+                        class: "flex flex-row justify-between mt-6"
+                        a {
+                            href: "https://www.dioxuslabs.com"
+                            class: "underline"
+                            "Made with dioxus"
+                        }
+                    }
+                }
+            }
+        }
+    })
+};

+ 43 - 0
packages/web/examples/landingpage2.rs

@@ -0,0 +1,43 @@
+//! Basic example that renders a simple domtree to the browser.
+
+use std::rc::Rc;
+
+use dioxus_core::prelude::*;
+use dioxus_web::*;
+fn main() {
+    // Setup logging
+    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));
+}
+
+static App: FC<()> = |ctx, _| {
+    let (contents, set_contents) = use_state(&ctx, || "asd");
+
+    ctx.render(rsx! {
+        div  {
+            class: "flex items-center justify-center flex-col"
+            div {
+                class: "flex items-center justify-center"
+                div {
+                    class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
+                    div { class: "font-bold text-xl", "Example Web app" }
+                    div { class: "text-sm text-gray-500", "This is running in your browser!" }
+                    div {
+                        class: "flex flex-row items-center justify-center mt-6"
+                        div { class: "font-medium text-6xl", "100%" }
+                    }
+                    div {
+                        class: "flex flex-row justify-between mt-6"
+                        a {
+                            href: "https://www.dioxuslabs.com"
+                            class: "underline"
+                            "Made with dioxus"
+                        }
+                    }
+                }
+            }
+        }
+    })
+};

+ 159 - 99
packages/web/examples/list.rs

@@ -1,125 +1,185 @@
-use std::collections::{BTreeMap, BTreeSet, HashMap};
-
-use dioxus::{events::on::MouseEvent, prelude::*};
 use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
 use dioxus_web::WebsysRenderer;
+use std::collections::BTreeMap;
 
 fn main() {
     wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
     console_error_panic_hook::set_once();
-    wasm_bindgen_futures::spawn_local(async move {
-        WebsysRenderer::new_with_props(App, ())
-            .run()
-            .await
-            .expect("major crash");
-    });
+    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
+}
+
+static APP_STYLE: &'static str = include_str!("./todomvc/style.css");
+
+#[derive(PartialEq, Clone, Copy)]
+pub enum FilterState {
+    All,
+    Active,
+    Completed,
 }
 
-use lazy_static::lazy_static;
-lazy_static! {
-    static ref DummyData: BTreeMap<usize, String> = {
-        let vals = vec![
-            "abc123", //
-            "abc124", //
-            "abc125", //
-            "abc126", //
-            "abc127", //
-            "abc128", //
-            "abc129", //
-            "abc1210", //
-            "abc1211", //
-            "abc1212", //
-            "abc1213", //
-            "abc1214", //
-            "abc1215", //
-            "abc1216", //
-            "abc1217", //
-            "abc1218", //
-            "abc1219", //
-            "abc1220", //
-            "abc1221", //
-            "abc1222", //
-        ];
-        vals.into_iter()
-            .map(ToString::to_string)
-            .enumerate()
-            .collect()
-    };
+#[derive(Debug, PartialEq, Clone)]
+pub struct TodoItem {
+    pub id: uuid::Uuid,
+    pub checked: bool,
+    pub contents: String,
 }
 
 static App: FC<()> = |ctx, _| {
-    let items = use_state_new(&ctx, || DummyData.clone());
-
-    // handle new elements
-    let add_new = move |_| {
-        items.modify(|m| {
-            let k = m.len();
-            let v = match (k % 3, k % 5) {
-                (0, 0) => "FizzBuzz".to_string(),
-                (0, _) => "Fizz".to_string(),
-                (_, 0) => "Buzz".to_string(),
-                _ => k.to_string(),
-            };
-            m.insert(k, v);
-        })
-    };
-
-    let elements = items.iter().map(|(k, v)| {
-        rsx! {
-            ListHelper {
-                name: k,
-                value: v
-                onclick: move |_| {
-                    let key = k.clone();
-                    items.modify(move |m| { m.remove(&key); } )
-                }
-            }
-        }
-    });
+    let (draft, set_draft) = use_state(&ctx, || "".to_string());
+    let (filter, set_filter) = use_state(&ctx, || FilterState::All);
+    let (is_editing, set_is_editing) = use_state(&ctx, || false);
+    // let (todos, set_todos) = use_state(&ctx, || BTreeMap::<String, TodoItem>::new());
+
+    // let todos = use_ref(&ctx, || BTreeMap::<String, TodoItem>::new());
+    let todos = use_state_new(&ctx, || BTreeMap::<uuid::Uuid, TodoItem>::new());
 
+    // let blah = "{draft}"
     ctx.render(rsx!(
         div {
-            h1 {"Some list"}
-            button {
-                "Remove all"
-                onclick: move |_| items.set(BTreeMap::new())
-            }
-            button {
-                "add new"
-                onclick: {add_new}
+            id: "app"
+            style { "{APP_STYLE}" }
+
+            div {
+                header {
+                    class: "header"
+                    h1 {"todos"}
+                    button {
+                        "press me"
+                        onclick: move |evt| {
+                            let contents = draft.clone();
+                            todos.modify(|f| {
+                                let id = uuid::Uuid::new_v4();
+                                f.insert(id.clone(), TodoItem {
+                                    id,
+                                    checked: false,
+                                    contents
+                                });
+                            })
+                        }
+                    }
+                    input {
+                        class: "new-todo"
+                        placeholder: "What needs to be done?"
+                        // value: "{draft}"
+                        oninput: move |evt| set_draft(evt.value)
+                    }
+                }
+
+                { // list
+                    todos
+                    .iter()
+                    .filter(|(id, item)| match filter {
+                        FilterState::All => true,
+                        FilterState::Active => !item.checked,
+                        FilterState::Completed => item.checked,
+                    })
+                    .map(|(id, todo)| {
+                        rsx!{
+                            li {
+                                "{todo.contents}"
+                                input {
+                                    class: "toggle"
+                                    type: "checkbox"
+                                    "{todo.checked}"
+                                }
+                                // {is_editing.then(|| rsx!(
+                                //     input {
+                                //         value: "{contents}"
+                                //     }
+                                // ))}
+                            }
+                        }
+                    })
+                }
+
+                // filter toggle (show only if the list isn't empty)
+                {(!todos.is_empty()).then(||
+                    rsx!{
+                        footer {
+                            span {
+                                strong {"10"}
+                                span {"0 items left"}
+                            }
+                            ul {
+                                class: "filters"
+                            {[
+                                    ("All", "", FilterState::All),
+                                    ("Active", "active", FilterState::Active),
+                                    ("Completed", "completed", FilterState::Completed),
+                                ]
+                                .iter()
+                                .map(|(name, path, filter)| {
+                                    rsx!(
+                                        li {
+                                            class: "{name}"
+                                            a {
+                                                href: "{path}"
+                                                onclick: move |_| set_filter(filter.clone())
+                                                "{name}"
+                                            }
+                                        }
+                                    )
+                                })
+                            }}
+                        }
+                    }
+                )}
             }
-            ul {
-                {elements}
+
+
+            footer {
+                class: "info"
+                p {"Double-click to edit a todo"}
+                p {
+                    "Created by "
+                    a { "jkelleyrtp", href: "http://github.com/jkelleyrtp/" }
+                }
+                p {
+                    "Part of "
+                    a { "TodoMVC", href: "http://todomvc.com" }
+                }
             }
         }
     ))
 };
 
-#[derive(Props)]
-struct ListProps<'a, F: Fn(MouseEvent) + 'a> {
-    name: &'a usize,
-    value: &'a str,
-    onclick: F,
-}
+pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
+    // let reducer = recoil::use_callback(&ctx, || ());
+    // let items_left = recoil::use_atom_family(&ctx, &TODOS, uuid::Uuid::new_v4());
 
-impl<F: Fn(MouseEvent)> PartialEq for ListProps<'_, F> {
-    fn eq(&self, other: &Self) -> bool {
-        // no references are ever the same
-        false
-    }
-}
+    let toggles = [
+        ("All", "", FilterState::All),
+        ("Active", "active", FilterState::Active),
+        ("Completed", "completed", FilterState::Completed),
+    ]
+    .iter()
+    .map(|(name, path, _filter)| {
+        rsx!(
+            li {
+                class: "{name}"
+                a {
+                    href: "{path}"
+                    // onclick: move |_| reducer.set_filter(&filter)
+                    "{name}"
+                }
+            }
+        )
+    });
+
+    // todo
+    let item_text = "";
+    let items_left = "";
 
-fn ListHelper<F: Fn(MouseEvent)>(ctx: Context, props: &ListProps<F>) -> DomTree {
-    let k = props.name;
-    let v = props.value;
     ctx.render(rsx! {
-        li {
-            class: "flex items-center text-xl"
-            key: "{k}"
-            span { "{k}: {v}" }
-            button {
-                "__ Remove"
-                onclick: {&props.onclick}
+        footer {
+            span {
+                strong {"{items_left}"}
+                span {"{item_text} left"}
+            }
+            ul {
+                class: "filters"
+                {toggles}
             }
         }
     })

+ 7 - 9
packages/web/examples/todomvc/filtertoggles.rs

@@ -13,16 +13,14 @@ pub fn FilterToggles(ctx: Context, props: &()) -> DomTree {
     ]
     .iter()
     .map(|(name, path, filter)| {
-        rsx!(
-            li {
-                class: "{name}"
-                a {
-                    href: "{path}"
-                    onclick: move |_| reducer.set_filter(&filter)
-                    "{name}"
-                }
+        rsx!(li {
+            class: "{name}"
+            a {
+                "{name}"
+                href: "{path}"
+                onclick: move |_| reducer.set_filter(&filter)
             }
-        )
+        })
     });
 
     // todo

+ 1 - 1
packages/web/examples/todomvc/todoitem.rs

@@ -21,7 +21,7 @@ pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
             }
             {is_editing.then(|| rsx!(
                 input {
-                    value: "{contents}"
+                    value: "{todo.contents}"
                 }
             ))}
         }

+ 0 - 7
packages/web/examples/todomvc_simple.rs

@@ -101,13 +101,6 @@ pub struct TodoEntryProps {
     item: Rc<TodoItem>,
 }
 
-mod mac {
-    #[macro_export]
-    macro_rules! TodoEntry {
-        () => {};
-    }
-}
-
 // pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
 // #[inline_props]
 pub fn TodoEntry(

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

@@ -115,6 +115,7 @@ pub struct TodoEntryProps {
 pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree {
     let (is_editing, set_is_editing) = use_state(&ctx, || false);
     let todo = use_atom_family(&ctx, &TODOS, props.id);
+    let contents = "";
 
     ctx.render(rsx! (
         li {

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

@@ -17,7 +17,7 @@ fn main() {
                         <div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
                             // Title
                             <div class="font-bold text-xl">
-                                "Jon's awesome site!!11"
+                                "Jon's awesome site!!"
                             </div>
 
                             // Subtext / description
@@ -45,4 +45,3 @@ fn main() {
         })
     }));
 }
-

+ 8 - 2
packages/web/src/interpreter.rs

@@ -542,8 +542,14 @@ fn virtual_event_from_websys_event(event: &web_sys::Event) -> VirtualEvent {
 
         "change" | "input" | "invalid" | "reset" | "submit" => {
             // is a special react events
-            // let evt: web_sys::FormEvent = event.clone().dyn_into().unwrap();
-            todo!()
+            let evt: web_sys::InputEvent = event.clone().dyn_into().expect("wrong event type");
+            let value: Option<String> = (&evt).data();
+            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
+            VirtualEvent::FormEvent(FormEvent { value })
         }
 
         "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"

+ 1 - 1
packages/web/src/lib.rs

@@ -5,11 +5,11 @@
 use dioxus::prelude::Properties;
 use fxhash::FxHashMap;
 use web_sys::{window, Document, Element, Event, Node};
+// use futures::{channel::mpsc, SinkExt, StreamExt};
 
 use dioxus::virtual_dom::VirtualDom;
 pub use dioxus_core as dioxus;
 use dioxus_core::{events::EventTrigger, prelude::FC};
-// use futures::{channel::mpsc, SinkExt, StreamExt};
 
 pub use dioxus_core::prelude;
 pub mod interpreter;

+ 1 - 1
packages/webview/Cargo.toml

@@ -11,7 +11,7 @@ license = "MIT/Apache-2.0"
 [dependencies]
 # web-view = { git = "https://github.com/Boscop/web-view" }
 web-view = "0.7.3"
-dioxus-core = { path = "../core", version = "0.1.2" }
+dioxus-core = { path = "../core", version = "0.1.2", features = ["serde"] }
 anyhow = "1.0.38"
 argh = "0.1.4"
 serde = "1.0.120"

+ 25 - 32
packages/webview/examples/demo.rs

@@ -4,50 +4,43 @@ use dioxus_core::prelude::*;
 
 fn main() {
     dioxus_webview::launch(
-        // Customize the webview
         |builder| {
             builder
                 .title("Test Dioxus App")
                 .size(320, 480)
-                .resizable(true)
+                .resizable(false)
                 .debug(true)
         },
-        // Props
         (),
-        // Draw the root component
         Example,
     )
     .expect("Webview finished");
 }
 
 static Example: FC<()> = |ctx, _props| {
-    ctx.render(html! {
-        <div>
-            <div class="flex items-center justify-center flex-col">
-                <div class="flex items-center justify-center">
-                    <div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
-                        // Title
-                        <div class="font-bold text-xl"> "Jon's awesome site!!11" </div>
-
-                        // Subtext / description
-                        <div class="text-sm text-gray-500"> "He worked so hard on it :)" </div>
-
-                        <div class="flex flex-row items-center justify-center mt-6">
-                            // Main number
-                            <div class="font-medium text-6xl">
-                                "1337"
-                            </div>
-                        </div>
-
-                        // Try another
-                        <div class="flex flex-row justify-between mt-6">
-                            <a href="http://localhost:8080/fib/{}" class="underline">
-                                "Legit made my own React"
-                            </a>
-                        </div>
-                    </div>
-                </div>
-            </div>
-        </div>
+    ctx.render(rsx! {
+        div  {
+            class: "flex items-center justify-center flex-col"
+            div {
+                class: "flex items-center justify-center"
+                div {
+                    class: "flex flex-col bg-white rounded p-4 w-full max-w-xs"
+                    div { class: "font-bold text-xl", "Example desktop app" }
+                    div { class: "text-sm text-gray-500", "This is running natively" }
+                    div {
+                        class: "flex flex-row items-center justify-center mt-6"
+                        div { class: "font-medium text-6xl", "100%" }
+                    }
+                    div {
+                        class: "flex flex-row justify-between mt-6"
+                        a {
+                            href: "https://www.dioxuslabs.com"
+                            class: "underline"
+                            "Made with dioxus"
+                        }
+                    }
+                }
+            }
+        }
     })
 };