Selaa lähdekoodia

fix: really big bug around hooks

Jonathan Kelley 3 vuotta sitten
vanhempi
commit
52c7154

+ 74 - 72
packages/core-macro/src/router.rs

@@ -1,3 +1,5 @@
+#![allow(dead_code)]
+
 use proc_macro2::TokenStream;
 use quote::quote;
 use syn::parse::{Parse, ParseStream};
@@ -109,85 +111,85 @@ fn parse_variants_attributes(
 }
 
 impl Routable {
-    fn build_from_path(&self) -> TokenStream {
-        let from_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
-            let ident = &variant.ident;
-            let right = match &variant.fields {
-                Fields::Unit => quote! { Self::#ident },
-                Fields::Named(field) => {
-                    let fields = field.named.iter().map(|it| {
-                        //named fields have idents
-                        it.ident.as_ref().unwrap()
-                    });
-                    quote! { Self::#ident { #(#fields: params.get(stringify!(#fields))?.parse().ok()?,)* } }
-                }
-                Fields::Unnamed(_) => unreachable!(), // already checked
-            };
-
-            let left = self.ats.get(i).unwrap();
-            quote! {
-                #left => ::std::option::Option::Some(#right)
-            }
-        });
-
-        quote! {
-            fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option<Self> {
-                match path {
-                    #(#from_path_matches),*,
-                    _ => ::std::option::Option::None,
-                }
-            }
-        }
-    }
-
-    fn build_to_path(&self) -> TokenStream {
-        let to_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
-            let ident = &variant.ident;
-            let mut right = self.ats.get(i).unwrap().value();
-
-            match &variant.fields {
-                Fields::Unit => quote! { Self::#ident => ::std::string::ToString::to_string(#right) },
-                Fields::Named(field) => {
-                    let fields = field
-                        .named
-                        .iter()
-                        .map(|it| it.ident.as_ref().unwrap())
-                        .collect::<Vec<_>>();
-
-                    for field in fields.iter() {
-                        // :param -> {param}
-                        // so we can pass it to `format!("...", param)`
-                        right = right.replace(&format!(":{}", field), &format!("{{{}}}", field))
-                    }
-
-                    quote! {
-                        Self::#ident { #(#fields),* } => ::std::format!(#right, #(#fields = #fields),*)
-                    }
-                }
-                Fields::Unnamed(_) => unreachable!(), // already checked
-            }
-        });
-
-        quote! {
-            fn to_path(&self) -> ::std::string::String {
-                match self {
-                    #(#to_path_matches),*,
-                }
-            }
-        }
-    }
+    // fn build_from_path(&self) -> TokenStream {
+    //     let from_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
+    //         let ident = &variant.ident;
+    //         let right = match &variant.fields {
+    //             Fields::Unit => quote! { Self::#ident },
+    //             Fields::Named(field) => {
+    //                 let fields = field.named.iter().map(|it| {
+    //                     //named fields have idents
+    //                     it.ident.as_ref().unwrap()
+    //                 });
+    //                 quote! { Self::#ident { #(#fields: params.get(stringify!(#fields))?.parse().ok()?,)* } }
+    //             }
+    //             Fields::Unnamed(_) => unreachable!(), // already checked
+    //         };
+
+    //         let left = self.ats.get(i).unwrap();
+    //         quote! {
+    //             #left => ::std::option::Option::Some(#right)
+    //         }
+    //     });
+
+    //     quote! {
+    //         fn from_path(path: &str, params: &::std::collections::HashMap<&str, &str>) -> ::std::option::Option<Self> {
+    //             match path {
+    //                 #(#from_path_matches),*,
+    //                 _ => ::std::option::Option::None,
+    //             }
+    //         }
+    //     }
+    // }
+
+    // fn build_to_path(&self) -> TokenStream {
+    //     let to_path_matches = self.variants.iter().enumerate().map(|(i, variant)| {
+    //         let ident = &variant.ident;
+    //         let mut right = self.ats.get(i).unwrap().value();
+
+    //         match &variant.fields {
+    //             Fields::Unit => quote! { Self::#ident => ::std::string::ToString::to_string(#right) },
+    //             Fields::Named(field) => {
+    //                 let fields = field
+    //                     .named
+    //                     .iter()
+    //                     .map(|it| it.ident.as_ref().unwrap())
+    //                     .collect::<Vec<_>>();
+
+    //                 for field in fields.iter() {
+    //                     // :param -> {param}
+    //                     // so we can pass it to `format!("...", param)`
+    //                     right = right.replace(&format!(":{}", field), &format!("{{{}}}", field))
+    //                 }
+
+    //                 quote! {
+    //                     Self::#ident { #(#fields),* } => ::std::format!(#right, #(#fields = #fields),*)
+    //                 }
+    //             }
+    //             Fields::Unnamed(_) => unreachable!(), // already checked
+    //         }
+    //     });
+
+    //     quote! {
+    //         fn to_path(&self) -> ::std::string::String {
+    //             match self {
+    //                 #(#to_path_matches),*,
+    //             }
+    //         }
+    //     }
+    // }
 }
 
 pub fn routable_derive_impl(input: Routable) -> TokenStream {
     let Routable {
-        ats,
-        not_found_route,
-        ident,
+        // ats,
+        // not_found_route,
+        // ident,
         ..
     } = &input;
 
-    let from_path = input.build_from_path();
-    let to_path = input.build_to_path();
+    // let from_path = input.build_from_path();
+    // let to_path = input.build_to_path();
 
     quote! {
         // #[automatically_derived]

+ 1 - 1
packages/core/Cargo.toml

@@ -28,7 +28,7 @@ smallvec = "1.6.1"
 
 slab = "0.4.3"
 
-futures-channel = "0.3.16"
+futures-channel = "0.3.18"
 
 # used for noderefs
 once_cell = "1.8.0"

+ 6 - 0
packages/core/src/diff.rs

@@ -473,6 +473,12 @@ impl<'bump> DiffState<'bump> {
         // TODO: add noderefs to current noderef list Noderefs
         let _new_component = self.scopes.get_scope(&new_idx).unwrap();
 
+        log::debug!(
+            "initializing component {:?} with height {:?}",
+            new_idx,
+            height + 1
+        );
+
         // Run the scope for one iteration to initialize it
         if self.scopes.run_scope(&new_idx) {
             // Take the node that was just generated from running the component

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

@@ -315,18 +315,10 @@ impl ScopeArena {
 
         let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") };
 
-        // log::debug!("found scope, about to run: {:?}", id);
-
         // Safety:
         // - We dropped the listeners, so no more &mut T can be used while these are held
         // - All children nodes that rely on &mut T are replaced with a new reference
-        scope.hook_vals.get_mut().drain(..).for_each(|state| {
-            let as_mut = unsafe { &mut *state };
-            let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
-            drop(boxed);
-        });
         scope.hook_idx.set(0);
-        scope.hook_arena.reset();
 
         // Safety:
         // - We've dropped all references to the wip bump frame with "ensure_drop_safety"

+ 13 - 2
packages/core/src/virtual_dom.rs

@@ -10,7 +10,7 @@ use indexmap::IndexSet;
 use smallvec::SmallVec;
 use std::{any::Any, collections::VecDeque, pin::Pin, sync::Arc, task::Poll};
 
-/// A virtual node system that progresses user events and diffs UI trees.
+/// A virtual node s ystem that progresses user events and diffs UI trees.
 ///
 ///
 /// ## Guide
@@ -347,8 +347,13 @@ impl VirtualDom {
     pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
         let mut committed_mutations = vec![];
 
+        log::debug!(
+            "Working with deadline. \nDirty scopes: {:?}. \nPending messages: {:?}",
+            self.dirty_scopes,
+            self.pending_messages
+        );
+
         while !self.dirty_scopes.is_empty() {
-            // log::debug!("working with deadline");
             let scopes = &self.scopes;
             let mut diff_state = DiffState::new(scopes);
 
@@ -357,6 +362,7 @@ impl VirtualDom {
             // Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
             self.dirty_scopes
                 .retain(|id| scopes.get_scope(id).is_some());
+
             self.dirty_scopes.sort_by(|a, b| {
                 let h1 = scopes.get_scope(a).unwrap().height;
                 let h2 = scopes.get_scope(b).unwrap().height;
@@ -364,6 +370,7 @@ impl VirtualDom {
             });
 
             if let Some(scopeid) = self.dirty_scopes.pop() {
+                log::debug!("Analyzing dirty scope {:?}", scopeid);
                 if !ran_scopes.contains(&scopeid) {
                     ran_scopes.insert(scopeid);
 
@@ -372,6 +379,7 @@ impl VirtualDom {
                             self.scopes.wip_head(&scopeid),
                             self.scopes.fin_head(&scopeid),
                         );
+                        log::debug!("Diffing old: {:?}, new: {:?}", old, new);
                         diff_state.stack.push(DiffInstruction::Diff { new, old });
                         diff_state.stack.scope_stack.push(scopeid);
 
@@ -391,9 +399,11 @@ impl VirtualDom {
                 for scope in seen_scopes {
                     self.dirty_scopes.remove(&scope);
                 }
+                log::debug!("Working generated mutations: {:?}", mutations);
 
                 committed_mutations.push(mutations);
             } else {
+                log::debug!("Could not finish work in time");
                 // leave the work in an incomplete state
                 return committed_mutations;
             }
@@ -535,6 +545,7 @@ impl VirtualDom {
     }
 }
 
+#[derive(Debug)]
 pub enum SchedulerMsg {
     // events from the host
     UiEvent(UserEvent),

+ 8 - 0
packages/coroutines/Cargo.toml

@@ -0,0 +1,8 @@
+[package]
+name = "dioxus-coroutines"
+version = "0.0.0"
+edition = "2018"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]

+ 8 - 0
packages/coroutines/src/lib.rs

@@ -0,0 +1,8 @@
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn it_works() {
+        let result = 2 + 2;
+        assert_eq!(result, 4);
+    }
+}

+ 1 - 1
packages/desktop/Cargo.toml

@@ -17,7 +17,7 @@ thiserror = "1.0.23"
 log = "0.4.13"
 html-escape = "0.2.9"
 wry = "0.12.2"
-futures-channel = "0.3.16"
+futures-channel = "0.3.18"
 tokio = { version = "1.12.0", features = [
     "sync",
     "rt-multi-thread",

+ 3 - 1
packages/desktop/examples/async.rs

@@ -11,14 +11,16 @@ use dioxus_hooks::*;
 use dioxus_html as dioxus_elements;
 
 fn main() {
+    simple_logger::init().unwrap();
     dioxus_desktop::launch(App, |c| c);
 }
 
 static App: FC<()> = |cx, props| {
     let mut count = use_state(cx, || 0);
+    log::debug!("count is {:?}", count);
 
     cx.push_task(|| async move {
-        tokio::time::sleep(Duration::from_millis(100)).await;
+        tokio::time::sleep(Duration::from_millis(1000)).await;
         count += 1;
     });
 

+ 16 - 12
packages/desktop/src/cfg.rs

@@ -1,14 +1,7 @@
-use std::ops::{Deref, DerefMut};
-
 use dioxus_core::DomEdit;
 use wry::{
-    application::{
-        error::OsError,
-        event_loop::{EventLoop, EventLoopWindowTarget},
-        menu::MenuBar,
-        window::{Fullscreen, Icon, Window, WindowBuilder},
-    },
-    webview::{RpcRequest, RpcResponse, WebView},
+    application::{event_loop::EventLoop, window::WindowBuilder},
+    webview::WebView,
 };
 
 pub struct DesktopConfig<'a> {
@@ -22,7 +15,7 @@ impl<'a> DesktopConfig<'a> {
     /// Initializes a new `WindowBuilder` with default values.
     #[inline]
     pub fn new() -> Self {
-        let mut window = WindowBuilder::new().with_title("Dioxus app");
+        let window = WindowBuilder::new().with_title("Dioxus app");
         Self {
             event_handler: None,
             window,
@@ -41,13 +34,24 @@ impl<'a> DesktopConfig<'a> {
         self
     }
 
-    pub fn with_window(&mut self, f: impl FnOnce(WindowBuilder) -> WindowBuilder) -> &mut Self {
+    pub fn with_window(
+        &mut self,
+        configure: impl FnOnce(WindowBuilder) -> WindowBuilder,
+    ) -> &mut Self {
         // gots to do a swap because the window builder only takes itself as muy self
         // I wish more people knew about returning &mut Self
         let mut builder = WindowBuilder::default().with_title("Dioxus App");
         std::mem::swap(&mut self.window, &mut builder);
-        builder = f(builder);
+        builder = configure(builder);
         std::mem::swap(&mut self.window, &mut builder);
         self
     }
+
+    pub fn with_event_handler(
+        &mut self,
+        handler: impl Fn(&mut EventLoop<()>, &mut WebView) + 'static,
+    ) -> &mut Self {
+        self.event_handler = Some(Box::new(handler));
+        self
+    }
 }

+ 0 - 31
packages/desktop/src/dom.rs

@@ -1,31 +0,0 @@
-//! webview dom
-
-use dioxus_core::DomEdit;
-
-// pub struct WebviewRegistry {}
-
-// impl WebviewRegistry {
-//     pub fn new() -> Self {
-//         Self {}
-//     }
-// }
-
-pub struct WebviewDom<'bump> {
-    pub edits: Vec<DomEdit<'bump>>,
-    pub node_counter: u64,
-    // pub registry: WebviewRegistry,
-}
-impl WebviewDom<'_> {
-    pub fn new() -> Self {
-        Self {
-            edits: Vec::new(),
-            node_counter: 0,
-            // registry,
-        }
-    }
-
-    // // Finish using the dom (for its edit list) and give back the node and event registry
-    // pub fn consume(self) -> WebviewRegistry {
-    //     self.registry
-    // }
-}

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

@@ -1 +0,0 @@
-

+ 3 - 2
packages/desktop/src/escape.rs

@@ -16,7 +16,8 @@ use std::fmt::{self, Write};
 ///
 /// view.eval(&format!("callback({});", web_view::escape(string)));
 /// ```
-pub fn escape(string: &str) -> Escaper {
+#[allow(unused)]
+pub fn escape_js_string(string: &str) -> Escaper {
     Escaper(string)
 }
 
@@ -76,6 +77,6 @@ impl<'a> fmt::Display for Escaper<'a> {
 #[test]
 fn test() {
     let plain = "ABC \n\r' abc \\  \u{2028}   \u{2029}123";
-    let escaped = escape(plain).to_string();
+    let escaped = escape_js_string(plain).to_string();
     assert!(escaped == "'ABC \\n\\r\\' abc \\\\  \\u2028   \\u2029123'");
 }

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

@@ -1,10 +1,10 @@
 //! Convert a serialized event to an event Trigger
 //!
 
+use std::any::Any;
 use std::sync::Arc;
-use std::{any::Any, rc::Rc};
 
-use dioxus_core::{ElementId, EventPriority, ScopeId, UserEvent};
+use dioxus_core::{ElementId, EventPriority, UserEvent};
 use dioxus_html::on::*;
 
 #[derive(serde::Serialize, serde::Deserialize)]
@@ -16,11 +16,11 @@ struct ImEvent {
 }
 
 pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
-    let mut ims: Vec<ImEvent> = serde_json::from_value(val).unwrap();
+    let ims: Vec<ImEvent> = serde_json::from_value(val).unwrap();
+
     let ImEvent {
         event,
         mounted_dom_id,
-        // scope,
         contents,
     } = ims.into_iter().next().unwrap();
 

+ 180 - 201
packages/desktop/src/lib.rs

@@ -3,42 +3,32 @@
 //! Render the Dioxus VirtualDom using the platform's native WebView implementation.
 //!
 
-use std::borrow::BorrowMut;
-use std::cell::{Cell, RefCell};
-use std::collections::{HashMap, VecDeque};
-use std::ops::{Deref, DerefMut};
-use std::rc::Rc;
-use std::sync::atomic::AtomicBool;
-use std::sync::mpsc::channel;
-use std::sync::{Arc, RwLock};
+mod cfg;
+mod escape;
+mod events;
+// mod desktop_context;
 
 use cfg::DesktopConfig;
 use dioxus_core::*;
-use serde::{Deserialize, Serialize};
-
-pub use wry;
-
-use wry::application::accelerator::{Accelerator, SysMods};
-use wry::application::event::{ElementState, Event, StartCause, WindowEvent};
-use wry::application::event_loop::{self, ControlFlow, EventLoop, EventLoopWindowTarget};
-use wry::application::keyboard::{Key, KeyCode, ModifiersState};
-use wry::application::menu::{MenuBar, MenuItem, MenuItemAttributes};
-use wry::application::window::{Fullscreen, WindowId};
-use wry::webview::{WebView, WebViewBuilder};
+use std::{
+    collections::{HashMap, VecDeque},
+    sync::atomic::AtomicBool,
+    sync::{Arc, RwLock},
+};
+use tokio::task::LocalSet;
 use wry::{
-    application::menu,
-    application::window::{Window, WindowBuilder},
-    webview::{RpcRequest, RpcResponse},
+    application::{
+        accelerator::{Accelerator, SysMods},
+        event::{Event, StartCause, WindowEvent},
+        event_loop::{ControlFlow, EventLoop, EventLoopWindowTarget},
+        keyboard::{KeyCode, ModifiersState},
+        menu::{MenuBar, MenuItem},
+        window::{Window, WindowId},
+    },
+    webview::RpcRequest,
+    webview::{WebView, WebViewBuilder},
 };
 
-mod cfg;
-mod desktop_context;
-mod dom;
-mod escape;
-mod events;
-
-static HTML_CONTENT: &'static str = include_str!("./index.html");
-
 pub fn launch(
     root: FC<()>,
     config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
@@ -54,7 +44,7 @@ pub fn launch_with_props<P: Properties + 'static + Send + Sync>(
     run(root, props, builder)
 }
 
-#[derive(Serialize)]
+#[derive(serde::Serialize)]
 struct Response<'a> {
     pre_rendered: Option<String>,
     edits: Vec<DomEdit<'a>>,
@@ -65,93 +55,43 @@ pub fn run<T: 'static + Send + Sync>(
     props: T,
     user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
 ) {
-    // Generate the config
-    let mut cfg = DesktopConfig::new();
-    user_builder(&mut cfg);
-    let DesktopConfig {
-        window: window_cfg,
-        manual_edits,
-        pre_rendered,
-        ..
-    } = cfg;
-
-    // All of our webview windows are stored in a way that we can look them up later
-    // The "DesktopContext" will provide functionality for spawning these windows
-    let mut webviews = HashMap::<WindowId, WebView>::new();
-    let event_loop = EventLoop::new();
-
-    let props_shared = Cell::new(Some(props));
-
-    // create local modifier state
-    let modifiers = ModifiersState::default();
+    let mut desktop_cfg = DesktopConfig::new();
+    user_builder(&mut desktop_cfg);
 
+    let mut state = DesktopController::new_on_tokio(root, props);
     let quit_hotkey = Accelerator::new(SysMods::Cmd, KeyCode::KeyQ);
-
-    let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
-    let is_ready: Arc<AtomicBool> = Default::default();
-
-    let mut frame = 0;
+    let modifiers = ModifiersState::default();
+    let event_loop = EventLoop::new();
 
     event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
 
         match window_event {
-            Event::NewEvents(StartCause::Init) => {
-                let window = create_window(event_loop, &window_cfg);
-                let window_id = window.id();
-                let sender =
-                    launch_vdom_with_tokio(root, props_shared.take().unwrap(), edit_queue.clone());
-                let webview = create_webview(window, is_ready.clone(), sender);
-                webviews.insert(window_id, webview);
-            }
+            Event::NewEvents(StartCause::Init) => state.new_window(&desktop_cfg, event_loop),
 
             Event::WindowEvent {
                 event, window_id, ..
             } => match event {
                 WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
-                WindowEvent::Destroyed { .. } => {
-                    webviews.remove(&window_id);
-                    if webviews.is_empty() {
-                        *control_flow = ControlFlow::Exit;
-                    }
-                }
-                WindowEvent::Moved(pos) => {
-                    //
-                }
+                WindowEvent::Destroyed { .. } => state.close_window(window_id, control_flow),
 
                 WindowEvent::KeyboardInput { event, .. } => {
                     if quit_hotkey.matches(&modifiers, &event.physical_key) {
-                        webviews.remove(&window_id);
-                        if webviews.is_empty() {
-                            *control_flow = ControlFlow::Exit;
-                        }
+                        state.close_window(window_id, control_flow);
                     }
                 }
 
                 WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
-                    if let Some(view) = webviews.get_mut(&window_id) {
+                    if let Some(view) = state.webviews.get_mut(&window_id) {
                         let _ = view.resize();
                     }
                 }
-                // TODO: we want to shuttle all of these events into the user's app
+
+                // TODO: we want to shuttle all of these events into the user's app or provide some handler
                 _ => {}
             },
 
-            Event::MainEventsCleared => {
-                // I hate this ready hack but it's needed to wait for the "onload" to occur
-                // We can't run any initializion scripts because the window isn't ready yet?
-                if is_ready.load(std::sync::atomic::Ordering::Relaxed) {
-                    let mut queue = edit_queue.write().unwrap();
-                    let (id, view) = webviews.iter_mut().next().unwrap();
-                    while let Some(edit) = queue.pop_back() {
-                        view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
-                            .unwrap();
-                    }
-                } else {
-                    println!("waiting for onload {:?}", frame);
-                    frame += 1;
-                }
-            }
+            Event::MainEventsCleared => state.try_load_ready_webviews(),
             Event::Resumed => {}
             Event::Suspended => {}
             Event::LoopDestroyed => {}
@@ -161,121 +101,160 @@ pub fn run<T: 'static + Send + Sync>(
     })
 }
 
-// Create a new tokio runtime on a dedicated thread and then launch the apps VirtualDom.
-pub(crate) fn launch_vdom_with_tokio<P: Send + 'static>(
-    root: FC<P>,
-    props: P,
-    edit_queue: Arc<RwLock<VecDeque<String>>>,
-) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
-    let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
-    let return_sender = sender.clone();
-
-    std::thread::spawn(move || {
-        // We create the runtim as multithreaded, so you can still "spawn" onto multiple threads
-        let runtime = tokio::runtime::Builder::new_multi_thread()
-            .enable_all()
-            .build()
-            .unwrap();
-
-        runtime.block_on(async move {
-            let mut dom = VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver);
-
-            let edits = dom.rebuild();
-
-            edit_queue
-                .write()
-                .unwrap()
-                .push_front(serde_json::to_string(&edits.edits).unwrap());
-
-            loop {
-                dom.wait_for_work().await;
-                let mut muts = dom.work_with_deadline(|| false);
-                while let Some(edit) = muts.pop() {
-                    edit_queue
-                        .write()
-                        .unwrap()
-                        .push_front(serde_json::to_string(&edit.edits).unwrap());
-                }
-            }
-        })
-    });
-
-    return_sender
+pub struct DesktopController {
+    webviews: HashMap<WindowId, WebView>,
+    sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
+    pending_edits: Arc<RwLock<VecDeque<String>>>,
+    quit_app_on_close: bool,
+    is_ready: Arc<AtomicBool>,
 }
 
-fn build_menu() -> MenuBar {
-    // create main menubar menu
-    let mut menu_bar_menu = MenuBar::new();
-
-    // create `first_menu`
-    let mut first_menu = MenuBar::new();
-
-    first_menu.add_native_item(MenuItem::About("Todos".to_string()));
-    first_menu.add_native_item(MenuItem::Services);
-    first_menu.add_native_item(MenuItem::Separator);
-    first_menu.add_native_item(MenuItem::Hide);
-    first_menu.add_native_item(MenuItem::HideOthers);
-    first_menu.add_native_item(MenuItem::ShowAll);
-
-    first_menu.add_native_item(MenuItem::Quit);
-    first_menu.add_native_item(MenuItem::CloseWindow);
+impl DesktopController {
+    // Launch the virtualdom on its own thread managed by tokio
+    // returns the desktop state
+    pub fn new_on_tokio<P: Send + 'static>(root: FC<P>, props: P) -> Self {
+        let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
+        let pending_edits = edit_queue.clone();
+
+        let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
+        let return_sender = sender.clone();
+
+        std::thread::spawn(move || {
+            // We create the runtim as multithreaded, so you can still "spawn" onto multiple threads
+            let runtime = tokio::runtime::Builder::new_multi_thread()
+                .enable_all()
+                .build()
+                .unwrap();
+
+            runtime.block_on(async move {
+                // LocalSet::new().block_on(&runtime, async move {
+                let mut dom =
+                    VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver);
+
+                let edits = dom.rebuild();
+
+                edit_queue
+                    .write()
+                    .unwrap()
+                    .push_front(serde_json::to_string(&edits.edits).unwrap());
+
+                loop {
+                    dom.wait_for_work().await;
+                    let mut muts = dom.work_with_deadline(|| false);
+                    while let Some(edit) = muts.pop() {
+                        log::debug!("found mutations {:?}", muts);
+                        edit_queue
+                            .write()
+                            .unwrap()
+                            .push_front(serde_json::to_string(&edit.edits).unwrap());
+                    }
+                }
+            })
+        });
 
-    // create second menu
-    let mut second_menu = MenuBar::new();
+        Self {
+            pending_edits,
+            sender: return_sender,
 
-    // second_menu.add_submenu("Sub menu", true, my_sub_menu);
-    second_menu.add_native_item(MenuItem::Copy);
-    second_menu.add_native_item(MenuItem::Paste);
-    second_menu.add_native_item(MenuItem::SelectAll);
+            webviews: HashMap::new(),
+            is_ready: Arc::new(AtomicBool::new(false)),
+            quit_app_on_close: true,
+        }
+    }
+
+    pub fn new_window(&mut self, cfg: &DesktopConfig, event_loop: &EventLoopWindowTarget<()>) {
+        let builder = cfg.window.clone().with_menu({
+            // create main menubar menu
+            let mut menu_bar_menu = MenuBar::new();
+
+            // create `first_menu`
+            let mut first_menu = MenuBar::new();
+
+            first_menu.add_native_item(MenuItem::About("Todos".to_string()));
+            first_menu.add_native_item(MenuItem::Services);
+            first_menu.add_native_item(MenuItem::Separator);
+            first_menu.add_native_item(MenuItem::Hide);
+            first_menu.add_native_item(MenuItem::HideOthers);
+            first_menu.add_native_item(MenuItem::ShowAll);
+
+            first_menu.add_native_item(MenuItem::Quit);
+            first_menu.add_native_item(MenuItem::CloseWindow);
+
+            // create second menu
+            let mut second_menu = MenuBar::new();
+
+            // second_menu.add_submenu("Sub menu", true, my_sub_menu);
+            second_menu.add_native_item(MenuItem::Copy);
+            second_menu.add_native_item(MenuItem::Paste);
+            second_menu.add_native_item(MenuItem::SelectAll);
+
+            menu_bar_menu.add_submenu("First menu", true, first_menu);
+            menu_bar_menu.add_submenu("Second menu", true, second_menu);
+
+            menu_bar_menu
+        });
+
+        let window = builder.build(event_loop).unwrap();
+        let window_id = window.id();
+
+        let (is_ready, sender) = (self.is_ready.clone(), self.sender.clone());
+
+        let webview = WebViewBuilder::new(window)
+            .unwrap()
+            .with_url("wry://index.html")
+            .unwrap()
+            .with_rpc_handler(move |_window: &Window, req: RpcRequest| {
+                match req.method.as_str() {
+                    "user_event" => {
+                        let event = events::trigger_from_serialized(req.params.unwrap());
+                        log::debug!("User event: {:?}", event);
+                        sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap();
+                    }
+                    "initialize" => {
+                        is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
+                    }
+                    _ => {}
+                }
+                // response always driven through eval.
+                // unfortunately, it seems to be pretty slow, so we might want to look into an RPC form
+                None
+            })
+            // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case"
+            // For now, we only serve two pieces of content which get included as bytes into the final binary.
+            .with_custom_protocol("wry".into(), move |request| {
+                let path = request.uri().replace("wry://", "");
+                let (data, meta) = match path.as_str() {
+                    "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"),
+                    "index.html/index.js" => {
+                        (include_bytes!("./index.js").to_vec(), "text/javascript")
+                    }
+                    _ => unimplemented!("path {}", path),
+                };
 
-    menu_bar_menu.add_submenu("First menu", true, first_menu);
-    menu_bar_menu.add_submenu("Second menu", true, second_menu);
+                wry::http::ResponseBuilder::new().mimetype(meta).body(data)
+            })
+            .build()
+            .unwrap();
 
-    menu_bar_menu
-}
+        self.webviews.insert(window_id, webview);
+    }
 
-fn create_window(event_loop: &EventLoopWindowTarget<()>, cfg: &WindowBuilder) -> Window {
-    let builder = cfg.clone().with_menu(build_menu());
-    builder.build(event_loop).unwrap()
-}
+    pub fn close_window(&mut self, window_id: WindowId, control_flow: &mut ControlFlow) {
+        self.webviews.remove(&window_id);
 
-fn create_webview(
-    window: Window,
-    is_ready: Arc<AtomicBool>,
-    sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
-) -> WebView {
-    WebViewBuilder::new(window)
-        .unwrap()
-        .with_url("wry://index.html")
-        .unwrap()
-        .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
-            match req.method.as_str() {
-                "user_event" => {
-                    let event = events::trigger_from_serialized(req.params.unwrap());
-                    log::debug!("User event: {:?}", event);
-                    sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap();
-                }
-                "initialize" => {
-                    is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
-                }
-                _ => {}
+        if self.webviews.is_empty() && self.quit_app_on_close {
+            *control_flow = ControlFlow::Exit;
+        }
+    }
+
+    pub fn try_load_ready_webviews(&mut self) {
+        if self.is_ready.load(std::sync::atomic::Ordering::Relaxed) {
+            let mut queue = self.pending_edits.write().unwrap();
+            let (_id, view) = self.webviews.iter_mut().next().unwrap();
+            while let Some(edit) = queue.pop_back() {
+                view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
+                    .unwrap();
             }
-            // always driven through eval
-            None
-        })
-        // .with_initialization_script(include_str!("./index.js"))
-        // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case"
-        // For now, we only serve two pieces of content which get included as bytes into the final binary.
-        .with_custom_protocol("wry".into(), move |request| {
-            let path = request.uri().replace("wry://", "");
-            let (data, meta) = match path.as_str() {
-                "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"),
-                "index.html/index.js" => (include_bytes!("./index.js").to_vec(), "text/javascript"),
-                _ => unimplemented!("path {}", path),
-            };
-
-            wry::http::ResponseBuilder::new().mimetype(meta).body(data)
-        })
-        .build()
-        .unwrap()
+        }
+    }
 }

+ 2 - 1
packages/hooks/Cargo.toml

@@ -7,4 +7,5 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../../packages/core", version = "0.1.2" }
+dioxus-core = { path = "../../packages/core", version = "0.1.3" }
+futures = "0.3.18"

+ 1 - 1
packages/hooks/README.md

@@ -19,7 +19,7 @@ You can always use it "normally" with the `split` method:
 let value = use_state(cx, || 10);
 
 // "Classic" usage:
-let (value, set_value) = use_state(cx, || 0).classic();
+let (value, set_value) = use_state(cx, || 0).split();
 ```
 
 ## use_ref

+ 3 - 0
packages/hooks/src/lib.rs

@@ -6,3 +6,6 @@ pub use useref::*;
 
 mod use_shared_state;
 pub use use_shared_state::*;
+
+mod usecoroutine;
+pub use usecoroutine::*;

+ 76 - 0
packages/hooks/src/usecoroutine.rs

@@ -0,0 +1,76 @@
+use dioxus_core::Context;
+use futures::Future;
+use std::{
+    borrow::Borrow,
+    cell::{Cell, RefCell},
+    pin::Pin,
+    rc::Rc,
+};
+
+pub fn use_coroutine<'a, F: Future<Output = ()> + 'a>(
+    cx: Context<'a>,
+    f: impl FnOnce() -> F + 'a,
+) -> CoroutineHandle {
+    //
+    cx.use_hook(
+        move |_| State {
+            running: Default::default(),
+            fut: Default::default(),
+            submit: Default::default(),
+        },
+        |state| {
+            let fut_slot = state.fut.clone();
+            let running = state.running.clone();
+            let submit: Box<dyn FnOnce() + 'a> = Box::new(move || {
+                let g = async move {
+                    running.set(true);
+                    f().await;
+                    running.set(false);
+                };
+                let p: Pin<Box<dyn Future<Output = ()>>> = Box::pin(g);
+                fut_slot
+                    .borrow_mut()
+                    .replace(unsafe { std::mem::transmute(p) });
+            });
+
+            let submit = unsafe { std::mem::transmute(submit) };
+            state.submit.get_mut().replace(submit);
+
+            if state.running.get() {
+                let mut fut = state.fut.borrow_mut();
+                cx.push_task(|| fut.as_mut().unwrap().as_mut());
+            } else {
+                // make sure to drop the old future
+                if let Some(fut) = state.fut.borrow_mut().take() {
+                    //
+                }
+            }
+            CoroutineHandle { cx, inner: state }
+        },
+    )
+}
+
+struct State {
+    running: Rc<Cell<bool>>,
+    submit: RefCell<Option<Box<dyn FnOnce()>>>,
+    fut: Rc<RefCell<Option<Pin<Box<dyn Future<Output = ()>>>>>>,
+}
+
+pub struct CoroutineHandle<'a> {
+    cx: Context<'a>,
+    inner: &'a State,
+}
+
+impl<'a> CoroutineHandle<'a> {
+    pub fn start(&self) {
+        if self.inner.running.get() {
+            return;
+        }
+        if let Some(submit) = self.inner.submit.borrow_mut().take() {
+            submit();
+            let mut fut = self.inner.fut.borrow_mut();
+            self.cx.push_task(|| fut.as_mut().unwrap().as_mut());
+        }
+    }
+    pub fn resume(&self) {}
+}

+ 14 - 7
packages/hooks/src/usestate.rs

@@ -1,7 +1,7 @@
 use dioxus_core::prelude::Context;
 use std::{
     cell::{Cell, Ref, RefCell, RefMut},
-    fmt::Display,
+    fmt::{Debug, Display},
     ops::Not,
     rc::Rc,
 };
@@ -54,11 +54,14 @@ pub fn use_state<'a, T: 'static>(
     initial_state_fn: impl FnOnce() -> T,
 ) -> UseState<'a, T> {
     cx.use_hook(
-        move |_| UseStateInner {
-            current_val: initial_state_fn(),
-            update_callback: cx.schedule_update(),
-            wip: Rc::new(RefCell::new(None)),
-            update_scheuled: Cell::new(false),
+        move |_| {
+            //
+            UseStateInner {
+                current_val: initial_state_fn(),
+                update_callback: cx.schedule_update(),
+                wip: Rc::new(RefCell::new(None)),
+                update_scheuled: Cell::new(false),
+            }
         },
         move |hook| {
             hook.update_scheuled.set(false);
@@ -91,7 +94,11 @@ where
         UseState { inner: self.inner }
     }
 }
-
+impl<T: Debug> Debug for UseState<'_, T> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{:?}", self.inner.current_val)
+    }
+}
 impl<'a, T: 'static> UseState<'a, T> {
     /// Tell the Dioxus Scheduler that we need to be processed
     pub fn needs_update(&self) {

+ 5 - 2
packages/router/Cargo.toml

@@ -22,7 +22,7 @@ web-sys = { version = "0.3", features = [
     "Window",
 ], optional = true }
 
-serde = "1"
+serde = "1.0.130"
 
 serde_urlencoded = "0.7"
 
@@ -30,13 +30,16 @@ wasm-bindgen = "0.2"
 js-sys = "0.3"
 gloo = "0.4"
 route-recognizer = "0.3.1"
+url = "2.2.2"
+url_serde = "0.2.0"
 
 
 [features]
-default = ["web"]
+default = ["web", "derive"]
 web = ["web-sys"]
 desktop = []
 mobile = []
+derive = []
 
 [dev-dependencies]
 console_error_panic_hook = "0.1.7"

+ 1 - 1
packages/router/README.md

@@ -9,7 +9,7 @@ Using the router should feel similar to tide's routing framework where an "addre
 Here's an example of how to use the router hook:
 
 ```rust
-#[derive(Clone, Routable)]
+#[derive(Clone, PartialEq, Serialize, Deserialize, Routable)]
 enum AppRoute {
     Home, 
     Posts,