diff --git a/Cargo.toml b/Cargo.toml index 2ab6b958f..a990ab171 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,9 @@ members = [ "packages/dioxus", "packages/recoil", "packages/docsite", + "packages/ssr", ] # "packages/webview", -# "packages/ssr", # "packages/cli", # "packages/webview", # "packages/hooks", diff --git a/packages/cli/Cargo.toml b/packages/cli/Cargo.toml index d75edf3f5..c5a88c46f 100644 --- a/packages/cli/Cargo.toml +++ b/packages/cli/Cargo.toml @@ -19,11 +19,11 @@ serde = "1.0.120" serde_json = "1.0.61" async-std = { version = "1.9.0", features = ["attributes"] } tide = "0.15.0" -cargo_toml = "0.8.1" fs_extra = "1.2.0" -notify = "5.0.0-pre.4" +cargo_toml = "0.8.1" futures = "0.3.12" +notify = "5.0.0-pre.4" rjdebounce = "0.2.1" tempfile = "3.2.0" diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index bca37a866..eeaec8b9d 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,13 +1,14 @@ use std::{ cell::{RefCell, UnsafeCell}, collections::HashMap, - rc::Rc, + sync::Arc, }; use generational_arena::Arena; use crate::innerlude::*; +type Rc = Arc; #[derive(Clone)] pub struct ScopeArena(Rc>); diff --git a/packages/core/src/debug_renderer.rs b/packages/core/src/debug_renderer.rs index a398897d3..52f3c0e7a 100644 --- a/packages/core/src/debug_renderer.rs +++ b/packages/core/src/debug_renderer.rs @@ -15,7 +15,7 @@ impl DebugRenderer { /// /// This means that the root component must either consumes its own context, or statics are used to generate the page. /// The root component can access things like routing in its context. - pub fn new(root: FC<()>) -> Self { + pub fn new(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) -> Self { Self::new_with_props(root, ()) } @@ -23,7 +23,10 @@ impl DebugRenderer { /// Automatically progresses the creation of the VNode tree to completion. /// /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom` - pub fn new_with_props(root: FC, root_props: T) -> Self { + pub fn new_with_props( + root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree + 'static, + root_props: T, + ) -> Self { Self::from_vdom(VirtualDom::new_with_props(root, root_props)) } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 095435ff5..cad6d04af 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -41,9 +41,11 @@ use std::{ cell::{RefCell, RefMut}, cmp::Ordering, collections::VecDeque, - rc::{Rc, Weak}, sync::atomic::AtomicU32, + // rc::{Rc, Weak}, + sync::{Arc, Weak}, }; +type Rc = Arc; /// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while /// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event. diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 4e5816ed2..544d59938 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -10,7 +10,9 @@ use crate::{ use bumpalo::Bump; use std::fmt::Debug; -use std::{any::Any, cell::RefCell, marker::PhantomData, rc::Rc}; +use std::sync::Arc; +use std::{any::Any, cell::RefCell, marker::PhantomData}; +type Rc = Arc; /// A domtree represents the result of "Viewing" the context /// It's a placeholder over vnodes, to make working with lifetimes easier diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index b9bead4f8..29a12145e 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -29,9 +29,12 @@ use std::{ fmt::Debug, future::Future, pin::Pin, - rc::{Rc, Weak}, + // rc::{Rc, Weak}, + sync::{Arc, Weak}, }; +type Rc = Arc; + /// An integrated virtual node system that progresses events and diffs UI trees. /// Differences are converted into patches which a renderer can use to draw the UI. pub struct VirtualDom { @@ -96,7 +99,7 @@ impl VirtualDom { /// /// let dom = VirtualDom::new(Example); /// ``` - pub fn new(root: FC<()>) -> Self { + pub fn new(root: impl Fn(Context, &()) -> DomTree + 'static) -> Self { Self::new_with_props(root, ()) } @@ -128,7 +131,10 @@ impl VirtualDom { /// /// let dom = VirtualDom::new(Example); /// ``` - pub fn new_with_props(root: FC

, root_props: P) -> Self { + pub fn new_with_props( + root: impl for<'a> Fn(Context<'a>, &'a P) -> DomTree + 'static, + root_props: P, + ) -> Self { let components = ScopeArena::new(Arena::new()); // Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents @@ -887,12 +893,11 @@ impl Scope { let shared_contexts = inner.shared_contexts.borrow(); if let Some(shared_ctx) = shared_contexts.get(&ty) { let rc = shared_ctx - .clone() - .downcast() + .downcast_ref() .expect("Should not fail, already validated the type from the hashmap"); *cached_root.borrow_mut() = Some(Rc::downgrade(&rc)); - return Ok(rc); + return Ok(rc.clone()); } else { match inner.parent { Some(parent_id) => { @@ -1086,4 +1091,18 @@ mod tests { }); // let root = dom.components.get(dom.base_scope).unwrap(); } + + // ensure the virtualdom is send + sync + // needed for use in async/await contexts + fn is_send_sync() { + fn check_send(a: T) -> T { + todo!() + } + fn check_sync(a: T) -> T { + todo!() + } + + let _ = check_send(VirtualDom::new(|ctx, props| ctx.render(rsx! { div {}}))); + let _ = check_sync(VirtualDom::new(|ctx, props| ctx.render(rsx! { div {}}))); + } } diff --git a/packages/recoil/.vscode/settings.json b/packages/recoil/.vscode/settings.json index 80f51cff8..2f418701b 100644 --- a/packages/recoil/.vscode/settings.json +++ b/packages/recoil/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": false + "rust-analyzer.inlayHints.enable": true } \ No newline at end of file diff --git a/packages/recoil/Cargo.toml b/packages/recoil/Cargo.toml index 30567fbca..7ef9e9636 100644 --- a/packages/recoil/Cargo.toml +++ b/packages/recoil/Cargo.toml @@ -7,7 +7,10 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.40" dioxus-core = { path = "../core" } +generational-arena = "0.2.8" +thiserror = "1.0.24" [dev-dependencies] uuid = { version = "0.8.2", features = ["v4"] } diff --git a/packages/recoil/README.md b/packages/recoil/README.md index adf3a4336..d388c0762 100644 --- a/packages/recoil/README.md +++ b/packages/recoil/README.md @@ -14,7 +14,7 @@ A simple atom of state is defined globally as a const: const Light: Atom<&'static str> = |_| "Green"; ``` -This atom of state is initialized with a value of `"Green"`. The atom that is returned does not actually contain any values. Instead, the atom's key - which is automatically generated in this instance - is used in the context of a Recoil App. +This atom of state is initialized with a value of `"Green"`. The atom that is returned does not actually contain any values. Instead, the atom's key - which is automatically generated in this instance - is used in the context of a Recoil App. This is then later used in components like so: @@ -23,7 +23,7 @@ fn App(ctx: Context, props: &()) -> DomTree { // The recoil root must be initialized at the top of the application before any use_recoil hooks recoil::init_recoil_root(&ctx, |_| {}); - let color = use_recoil(&ctx, &TITLE); + let color = recoil::use_read(ctx, Light); ctx.render(rsx!{ h1 {"Color of light: {color}"} diff --git a/packages/recoil/examples/api_mvc.rs b/packages/recoil/examples/api_mvc.rs index 480a2f449..b89fd0fae 100644 --- a/packages/recoil/examples/api_mvc.rs +++ b/packages/recoil/examples/api_mvc.rs @@ -29,9 +29,9 @@ impl TitleController { fn main() { wasm_bindgen_futures::spawn_local(dioxus_web::WebsysRenderer::start(|ctx, _| { - let title = use_recoil_value(ctx, &TITLE); - let subtitle = use_recoil_value(ctx, &SUBTITLE); - let controller = use_recoil_callback(ctx, TitleController::new); + let title = use_read(ctx, &TITLE); + let subtitle = use_read(ctx, &SUBTITLE); + let controller = use_recoil_api(ctx, TitleController::new); rsx! { in ctx, div { diff --git a/packages/recoil/examples/callback.rs b/packages/recoil/examples/callback.rs index b9bd74735..dd0c185b9 100644 --- a/packages/recoil/examples/callback.rs +++ b/packages/recoil/examples/callback.rs @@ -20,8 +20,8 @@ fn update_title(api: &RecoilApi) { } static App: FC<()> = |ctx, _| { - let title = use_recoil_value(ctx, &TITLE); - let next_light = use_recoil_callback(ctx, |api| move |_| update_title(&api)); + let title = use_read(ctx, &TITLE); + let next_light = use_recoil_api(ctx, |api| move |_| update_title(&api)); rsx! { in ctx, div { diff --git a/packages/recoil/examples/ecs.rs b/packages/recoil/examples/ecs.rs index 81a30f2e8..e09a80777 100644 --- a/packages/recoil/examples/ecs.rs +++ b/packages/recoil/examples/ecs.rs @@ -5,7 +5,7 @@ //! slicing up your state beyond atoms might be desirable. //! //! Instead of storing groups of entities in a collection of structs, the ECS Architecture instead stores -//! an array for each entity in the collection. This tends to improve performance for batch operations on +//! an array for each field in of a struct. This tends to improve performance for batch operations on //! individual fields at the cost of complexity. Fortunately, this ECS model is built right into Recoil, //! making it easier than ever to enable sharded datastructures in your app. //! @@ -16,6 +16,7 @@ //! //! This approach is best suited for applications where individual entries in families are very large //! and updates to neighbors are costly in terms of Clone or field comparisons for memoization. + use dioxus::prelude::*; use dioxus_core as dioxus; use recoil::*; @@ -31,8 +32,9 @@ const TODOS: EcsModel = |builder| {}; // const SELECT_SUBTITLE: SelectorBorrowed = |s, k| TODOS.field(1).select(k); static App: FC<()> = |ctx, _| { + use_init_recoil_root(ctx, |_| {}); + // let title = use_recoil_value(ctx, &C_SELECTOR); - dbg!(TODOS); let title = ""; rsx! { in ctx, diff --git a/packages/recoil/examples/family.rs b/packages/recoil/examples/family.rs index d9e300b6b..396c9bf90 100644 --- a/packages/recoil/examples/family.rs +++ b/packages/recoil/examples/family.rs @@ -14,9 +14,8 @@ struct Todo { } static App: FC<()> = |ctx, _| { - use_init_recoil_root(ctx); - - let todos = use_recoil_family(ctx, &TODOS); + use_init_recoil_root(ctx, |_| {}); + let todos = use_read_family(ctx, &TODOS); rsx! { in ctx, div { @@ -31,7 +30,7 @@ struct ChildProps { } static Child: FC = |ctx, props| { - let (todo, set_todo) = use_recoil_state(ctx, &TODOS.select(&props.id)); + let (todo, set_todo) = use_read_write(ctx, &TODOS.select(&props.id)); rsx! { in ctx, div { diff --git a/packages/recoil/examples/helloworld.rs b/packages/recoil/examples/helloworld.rs index 9fffacb7c..b8c730860 100644 --- a/packages/recoil/examples/helloworld.rs +++ b/packages/recoil/examples/helloworld.rs @@ -4,7 +4,9 @@ use recoil::*; const COUNT: Atom = |_| 0; static App: FC<()> = |ctx, _| { - let (count, set_count) = use_recoil_state(ctx, &COUNT); + use_init_recoil_root(ctx, |_| {}); + + let (count, set_count) = use_read_write(ctx, &COUNT); rsx! { in ctx, div { diff --git a/packages/recoil/examples/selectorlist.rs b/packages/recoil/examples/selectorlist.rs index 28f4afab8..ebe68b83b 100644 --- a/packages/recoil/examples/selectorlist.rs +++ b/packages/recoil/examples/selectorlist.rs @@ -18,10 +18,10 @@ const D_SELECTOR: SelectorFamilyBorrowed = |api, key| -> &i32 { }; static App: FC<()> = |ctx, _| { - use_init_recoil_root(ctx); + use_init_recoil_root(ctx, |_| {}); + + let title = use_read(ctx, &C_SELECTOR); - let title = use_recoil_value(ctx, &C_SELECTOR); - let title = ""; rsx! { in ctx, div { "{title}" diff --git a/packages/recoil/examples/selectors.rs b/packages/recoil/examples/selectors.rs index 707485676..8e5c46afb 100644 --- a/packages/recoil/examples/selectors.rs +++ b/packages/recoil/examples/selectors.rs @@ -6,7 +6,7 @@ const B: Atom = |_| 0; const C: Selector = |api| api.get(&A) + api.get(&B); static App: FC<()> = |ctx, _| { - use_init_recoil_root(ctx); + use_init_recoil_root(ctx, |_| {}); rsx! { in ctx, div { Banner {} @@ -17,12 +17,12 @@ static App: FC<()> = |ctx, _| { }; static Banner: FC<()> = |ctx, _| { - let count = use_recoil_value(ctx, &C); + let count = use_read(ctx, &C); ctx.render(rsx! { h1 { "Count: {count}" } }) }; static BtnA: FC<()> = |ctx, _| { - let (a, set) = use_recoil_state(ctx, &A); + let (a, set) = use_read_write(ctx, &A); rsx! { in ctx, div { "a" button { "+", onclick: move |_| set(a + 1) } @@ -32,7 +32,7 @@ static BtnA: FC<()> = |ctx, _| { }; static BtnB: FC<()> = |ctx, _| { - let (b, set) = use_recoil_state(ctx, &B); + let (b, set) = use_read_write(ctx, &B); rsx! { in ctx, div { "b" button { "+", onclick: move |_| set(b + 1) } diff --git a/packages/recoil/notes/Architecture.md b/packages/recoil/notes/Architecture.md index b64c7037c..5748a4fbb 100644 --- a/packages/recoil/notes/Architecture.md +++ b/packages/recoil/notes/Architecture.md @@ -1,16 +1,15 @@ # Architecture - - - ## ECS + It's often ideal to represent list-y state as an SoA (struct of arrays) instead of an AoS (array of structs). In 99% of apps, normal clone-y performance is fine. If you need more performance on top of cloning on update, IM.rs will provide fast immutable data structures. -But, if you need **extreme performance** consider the ECS (SoA) model. With ECS model, we can modify fields of an entry without invalidating selectors on neighboring fields. +But, if you need **extreme performance** consider the ECS (SoA) model. With ECS model, we can modify fields of an entry without invalidating selectors on neighboring fields. -This approach is for that 0.1% of apps that need peak performance. Our philosophy is that these tools should be available when needed, but you shouldn't need to reach for them often. An example use case might be a graphics editor or simulation engine where thousands of entities with many fields are rendered to the screen in realtime. +This approach is for that 0.1% of apps that need peak performance. Our philosophy is that these tools should be available when needed, but you shouldn't need to reach for them often. An example use case might be a graphics editor or simulation engine where thousands of entities with many fields are rendered to the screen in realtime. Recoil will even help you: + ```rust type TodoModel = ( String, // title @@ -24,6 +23,7 @@ const SELECT_SUBTITLE: SelectorBorrowed = |s, k| TODOS.field(1).selec ``` Or with a custom derive macro to take care of some boilerplate and maintain readability. This macro simply generates the type tuple from the model fields and then some associated constants for indexing them. + ```rust #[derive(EcsModel)] struct TodoModel { @@ -44,22 +44,69 @@ const SELECT_TITLE: SelectorBorrowed = |s, k| TODOS.field(TodoModel:: const SELECT_SUBTITLE: SelectorBorrowed = |s, k| TODOS.field(TodoModel::subtitle).select(k); ``` - - - - - - - - ## Optimization -Selectors and references. ----- -Because new values are inserted without touching the original, we can keep old values around. As such, it makes sense to allow borrowed data since we can continue to reference old data if the data itself hasn't changed. +## Selectors and references. -However, this does lead to a greater memory overhead, so occasionally we'll want to sacrifice an a component render in order to evict old values. +Because new values are inserted without touching the original, we can keep old values around. As such, it makes sense to allow borrowed data since we can continue to reference old data if the data itself hasn't changed. +However, this does lead to a greater memory overhead, so occasionally we'll want to sacrifice an a component render in order to evict old values. This will be done automatically for us due to the nature of Rc. +Also, we want selectors to have internal dependencies. +## Async Atoms +- make it so "waiting" data can be provided +- someone updates via a future IE `atom.set(|| async {})` +- RecoilJS handles this with a "loadable" that has different states (ready, waiting, err just like Rust's poll method on future) + - I think the old value is provided until the new value is ready(?) + - Integrates with suspense to directly await the contents +## Async atoms + +```rust +let (atom, set_atom, modify_atom) = (ATOM.use_read(ctx), ATOM.use_write(ctx), ATOM.use_modify(ctx)); + +const Title: AsyncAtom = |builder| { + builder.on_set(|api, new_val| async { + + }) + builder.on_get(|api| async { + + }) +} +``` + +## Async selectors + +```rust +struct ContentCard { + title: String, + content: String, +} +const ROOT_URL: Atom<&str> = |_| "localhost:8080"; + +// Since we don't plan on changing any content during the lifetime of the app, a selector works fine +const ContentCards: SelectorFamily = |api, key| api.on_get_async(async { + // Whenever the root_url changes, this atom will be re-evaluated + let root_url = api.get(&ROOT_URL); + let data: ContentCard = fetch(format!("{}/{}", root_url, key).await?.json().await?; + data +}) + +static ContentCard: FC<()> = |ctx, props| { + let body = async match use_recoil_value()(props.id).await { + Ok(content) => rsx!{ p {"{content}"} } + Err(e) => rsx!{ p {"Failed to load"}} + }; + + rsx!{ + div { + h1{ "Non-async data here" }} + Suspense { + content: {body} + fallback: { rsx!{ div {"Loading..."} } } + } + } + } +}; +``` diff --git a/packages/recoil/old/atom.rs b/packages/recoil/old/atom.rs deleted file mode 100644 index 00cc3e102..000000000 --- a/packages/recoil/old/atom.rs +++ /dev/null @@ -1,26 +0,0 @@ -use crate::{AtomValue, Readable, RecoilItem}; - -pub type Atom = fn(&mut AtomBuilder) -> T; - -impl Readable for Atom { - fn load(&'static self) -> RecoilItem { - todo!() - // RecoilItem::Atom(self as *const _ as _) - } -} - -pub struct AtomBuilder { - pub key: String, - _never: std::marker::PhantomData, -} - -impl AtomBuilder { - pub fn new() -> Self { - Self { - key: "".to_string(), - _never: std::marker::PhantomData {}, - } - } - - pub fn set_key(&mut self, _key: &'static str) {} -} diff --git a/packages/recoil/old/callback.rs b/packages/recoil/old/callback.rs deleted file mode 100644 index 93d8056ed..000000000 --- a/packages/recoil/old/callback.rs +++ /dev/null @@ -1,32 +0,0 @@ -use std::rc::Rc; - -use crate::Atom; - -pub struct RecoilApi {} - -impl RecoilApi { - /// Get the value of an atom. Returns a reference to the underlying data. - pub fn get(&self, t: &'static Atom) -> Rc { - todo!() - } - - /// Replace an existing value with a new value - /// - /// This does not replace the value instantly. - /// All calls to "get" will return the old value until the component is rendered. - pub fn set(&self, t: &'static Atom, new: T) { - self.modify(t, move |old| *old = new); - } - - /// Modify lets you modify the value in place. However, because there's no previous value around to compare - /// the new one with, we are unable to memoize the change. As such, all downsteam users of this Atom will - /// be updated, causing all subsrcibed components to re-render. - /// - /// This is fine for most values, but might not be performant when dealing with collections. For collections, - /// use the "Family" variants as these will stay memoized for inserts, removals, and modifications. - /// - /// Note - like "set" this won't propogate instantly. Once all "gets" are dropped, only then will the update occur - pub fn modify(&self, t: &'static Atom, f: impl FnOnce(&mut T) -> O) -> O { - todo!() - } -} diff --git a/packages/recoil/old/family.rs b/packages/recoil/old/family.rs deleted file mode 100644 index 8f73fcfc1..000000000 --- a/packages/recoil/old/family.rs +++ /dev/null @@ -1,26 +0,0 @@ -//! Provide a memoized wrapper around collections for efficient updates. -//! -------------------------------------------------------------------- -//! - -use crate::{AtomValue, FamilyKey, Readable, RecoilItem}; -#[allow(non_camel_case_types)] -pub struct atom_family(pub fn(&mut AtomFamilyBuilder)); -pub type AtomFamily = atom_family; - -// impl Readable for &'static AtomFamily { -// fn load(&self) -> RecoilItem { -// RecoilItem::Atom(*self as *const _ as _) -// } -// } - -pub struct AtomFamilyBuilder { - _never: std::marker::PhantomData<(K, V)>, -} - -impl atom_family { - fn select(&'static self, key: &K) -> FamilySelected { - todo!() - } -} - -struct FamilySelected {} diff --git a/packages/recoil/old/hooks.rs b/packages/recoil/old/hooks.rs deleted file mode 100644 index a8ba3e49f..000000000 --- a/packages/recoil/old/hooks.rs +++ /dev/null @@ -1,110 +0,0 @@ -use std::rc::Rc; - -use dioxus_core::{hooks::use_ref, prelude::Context}; - -use crate::{Atom, AtomValue, Readable, RecoilApi, RecoilRoot}; - -/// This hook initializes the Recoil Context - should only be placed once per app. -/// -/// -/// ```ignore -/// -/// -/// -/// ``` -pub fn init_root(ctx: Context) { - ctx.use_create_context(move || RecoilRoot::new()) -} - -/// Use an atom and its setter -/// -/// This hook subscribes the component to any updates to the atom. -/// -/// ```rust -/// const TITLE: Atom<&str> = atom(|_| "default"); -/// -/// static App: FC<()> = |ctx, props| { -/// let (title, set_title) = recoil::use_state(ctx, &TITLE); -/// ctx.render(rsx!{ -/// div { -/// "{title}" -/// button {"on", onclick: move |_| set_title("on")} -/// button {"off", onclick: move |_| set_title("off")} -/// } -/// }) -/// } -/// -/// ``` -pub fn use_recoil_state<'a, T: PartialEq + 'static>( - ctx: Context<'a>, - readable: &'static impl Readable, -) -> (&'a T, &'a Rc) { - struct RecoilStateInner { - root: Rc, - value: Rc, - setter: Rc, - } - - let root = ctx.use_context::(); - let (subscriber_id, value) = root.subscribe_consumer(readable, ctx.schedule_update()); - - ctx.use_hook( - move || RecoilStateInner { - value, - root: root.clone(), - setter: Rc::new(move |new_val| root.update_atom(readable, new_val)), - }, - move |hook| { - hook.value = hook.root.load_value(readable); - (hook.value.as_ref(), &hook.setter) - }, - // Make sure we unsubscribe - // It's not *wrong* for a dead component to receive updates, but it is less performant - move |hook| hook.root.drop_consumer(subscriber_id), - ) -} - -/// -/// -/// -/// ```ignore -/// let (title, set_title) = recoil::use_state() -/// -/// -/// -/// ``` -pub fn use_recoil_value<'a, T: PartialEq>(ctx: Context<'a>, t: &'static impl Readable) -> &'a T { - todo!() -} - -/// Update an atom's value without -/// -/// Enable the ability to set a value without subscribing the componet -/// -/// -/// -/// ```ignore -/// let (title, set_title) = recoil::use_state() -/// -/// -/// -/// ``` -pub fn use_set_state<'a, T: PartialEq>(c: Context<'a>, t: &'static Atom) -> &'a Rc { - todo!() -} - -/// -/// -/// -/// ```ignore -/// let (title, set_title) = recoil::use_state() -/// -/// -/// -/// ``` -pub fn use_recoil_callback<'a, F: 'a>( - ctx: Context<'a>, - f: impl Fn(RecoilApi) -> F + 'static, -) -> &F { - todo!() -} diff --git a/packages/recoil/old/lib.rs b/packages/recoil/old/lib.rs deleted file mode 100644 index 21ed373ec..000000000 --- a/packages/recoil/old/lib.rs +++ /dev/null @@ -1,15 +0,0 @@ -mod atom; -mod callback; -mod family; -mod hooks; -mod root; -mod selector; -mod traits; - -pub use atom::*; -pub use callback::*; -pub use family::*; -pub use hooks::*; -pub use root::*; -pub use selector::*; -pub use traits::*; diff --git a/packages/recoil/old/root.rs b/packages/recoil/old/root.rs deleted file mode 100644 index a587a35c2..000000000 --- a/packages/recoil/old/root.rs +++ /dev/null @@ -1,148 +0,0 @@ -use std::{ - any::Any, - cell::RefCell, - collections::HashMap, - rc::Rc, - sync::atomic::{AtomicU32, AtomicUsize}, -}; - -use crate::{Atom, AtomBuilder, AtomValue, Readable}; - -pub type OpaqueConsumerCallback = Box; - -struct Consumer { - callback: OpaqueConsumerCallback, -} - -static SUBSCRIBER_ID: AtomicU32 = AtomicU32::new(0); -fn next_id() -> u32 { - SUBSCRIBER_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed) -} - -type AtomId = u32; -type ConsumerId = u32; - -pub struct RecoilRoot { - consumers: RefCell>>, -} - -impl RecoilRoot { - pub(crate) fn new() -> Self { - Self { - consumers: Default::default(), - } - } - - // This is run by hooks when they register as a listener for a given atom - // Their ID is stored in the `atom_consumers` map which lets us know which consumers to update - // When the hook is dropped, they are unregistered from this map - // - // If the Atom they're registering as a listener for doesn't exist, then the atom - // is initialized. Otherwise, the most recent value of the atom is returned - // pub fn register_readable(&self, atom: &impl Readable) -> Rc { - // todo!() - // } - - /// This registers the updater fn to any updates to the readable - /// Whenever the readable changes, the updater Fn will be called. - /// - /// This also back-propogates changes, meaning components that update an atom - /// will be updated from their subscription instead of directly within their own hook. - pub fn subscribe_consumer( - &self, - atom: &'static impl Readable, - updater: impl Fn(), - // return the value and the consumer's ID - // the consumer needs to store its ID so it can properly unsubscribe from the value - ) -> (u32, Rc) { - let id = next_id(); - - // Get the raw static reference of the atom - let atom_ptr = get_atom_raw_ref(atom); - let mut consumers = self.consumers.borrow_mut(); - if !consumers.contains_key(&atom_ptr) { - // Run the atom's initialization - // let mut b = AtomBuilder::::new(); - // let inital_value = atom.0(&mut b); - - consumers.insert(atom_ptr, HashMap::new()); - } - - todo!() - - // Rc::new(inital_value) - // todo!() - // // Get the raw static reference of the atom - // let atom_ptr = get_atom_raw_ref(atom); - - // // Subcribe this hook_id to this atom - // let mut consumers = self.consumers.borrow_mut(); - // let hooks = consumers - // .get_mut(&atom_ptr) - // .expect("Atom must be initialized before being subscribed to"); - - // // Coerce into any by wrapping the updater in a box - // // (yes it's some weird indirection) - // let any_updater: OpaqueConsumerCallback = Box::new(updater); - // let consumer = Consumer { - // callback: any_updater, - // }; - - // Insert into the map, booting out the old consumer - // TODO @Jon, make the "Consumer" more efficient, patching its update mechanism in-place - // hooks.insert(hook_id, consumer); - } - - pub fn drop_consumer(&self, subscriber_id: u32) { - // let mut consumers = self.consumers.borrow_mut(); - // let atom_ptr = get_atom_raw_ref(atom); - // let atoms = consumers.get_mut(&atom_ptr); - - // if let Some(consumers) = atoms { - // let entry = consumers.remove_entry(&hook_id); - // if let Some(_) = entry { - // log::debug!("successfully unsubscribed listener"); - // } else { - // log::debug!("Failure to unsubscribe"); - // } - // } else { - // log::debug!("Strange error, atoms should be registed if the consumer is being dropped"); - // } - } - - pub fn update_atom(&self, atom: &impl Readable, new_val: T) { - // Get the raw static reference of the atom - // let atom_ptr = get_atom_raw_ref(atom); - - // let mut consumers = self.consumers.borrow_mut(); - // let hooks = consumers - // .get_mut(&atom_ptr) - // .expect("Atom needs to be registered before trying to update it"); - - // let new_val = Rc::new(new_val); - // for hook in hooks.values_mut() { - // let callback: &mut Rc> = hook - // .callback - // .downcast_mut::<_>() - // .expect("Wrong type of atom stored, internal error"); - - // callback(UpdateAction::Regenerate(new_val.clone())); - // } - } - - pub fn load_value(&self, atom: &impl Readable) -> Rc { - todo!() - } -} - -trait RecoilSlot {} - -// struct RecoilSlot { -// consumers: HashMap>, -// } - -fn get_atom_raw_ref(atom: &'static impl Readable) -> u32 { - let atom_ptr = atom as *const _; - let atom_ptr = atom_ptr as *const u32; - atom_ptr as u32 -} diff --git a/packages/recoil/old/selector.rs b/packages/recoil/old/selector.rs deleted file mode 100644 index 89e864922..000000000 --- a/packages/recoil/old/selector.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::Atom; - -// ===================================== -// Selectors -// ===================================== -pub struct SelectorApi {} -impl SelectorApi { - pub fn get(&self, t: &'static Atom) -> &T { - todo!() - } -} -// pub struct SelectorBuilder { -// _p: std::marker::PhantomData, -// } -// impl SelectorBuilder { -// pub fn getter(self, f: impl Fn(()) -> O) -> SelectorBuilder { -// todo!() -// // std::rc::Rc::pin(value) -// // todo!() -// } -// } - -pub struct selector(pub fn(&SelectorApi) -> O); -// pub struct selector(pub fn(SelectorBuilder) -> SelectorBuilder); -pub type Selector = selector; diff --git a/packages/recoil/old/traits.rs b/packages/recoil/old/traits.rs deleted file mode 100644 index 362c4a203..000000000 --- a/packages/recoil/old/traits.rs +++ /dev/null @@ -1,13 +0,0 @@ -use crate::{Atom, AtomFamily}; - -use std::hash::Hash; - -pub trait FamilyKey: PartialEq + Hash {} -impl FamilyKey for T {} - -pub trait AtomValue: PartialEq {} -impl AtomValue for T {} - -pub trait Readable: 'static { - fn load(&'static self) -> RecoilItem; -} diff --git a/packages/recoil/src/lib.rs b/packages/recoil/src/lib.rs index a879d8c7e..a4200f539 100644 --- a/packages/recoil/src/lib.rs +++ b/packages/recoil/src/lib.rs @@ -6,158 +6,443 @@ use std::{ rc::Rc, }; -pub trait FamilyKey: PartialEq + Hash {} -impl FamilyKey for T {} +pub use api::*; +pub use atomfamily::*; +pub use atoms::*; +pub use ecs::*; +use error::*; +pub use hooks::*; +pub use hooks::*; +pub use root::*; +pub use selector::*; +pub use selectorfamily::*; +pub use traits::*; +pub use utils::*; -pub trait AtomValue: PartialEq + Clone {} -impl AtomValue for T {} +mod traits { + use dioxus_core::prelude::Context; -// Atoms, selectors, and their family variants are readable -pub trait Readable {} + use super::*; + pub trait FamilyKey: PartialEq + Hash + 'static {} + impl FamilyKey for T {} -// Only atoms and atom families are writable -// Selectors are not -pub trait Writeable: Readable {} + pub trait AtomValue: PartialEq + 'static {} + impl AtomValue for T {} -// ================= -// Atoms -// ================= + // Atoms, selectors, and their family variants are readable + pub trait Readable: Sized + Copy { + fn use_read<'a>(self, ctx: Context<'a>) -> &'a T { + hooks::use_read(ctx, self); + todo!() + } -pub struct AtomBuilder {} -pub type Atom = fn(&mut AtomBuilder) -> T; -impl Readable for Atom {} -impl Writeable for Atom {} + // This returns a future of the value + // If the atom is currently pending, that future will resolve to pending + // If the atom is currently ready, the future will immediately resolve + // if the atom switches from ready to pending, the component will re-run, returning a pending future + fn use_read_async<'a>(self, ctx: Context<'a>) -> &'a T { + todo!() + } -pub type AtomFamily> = fn((K, V)) -> F; + fn initialize(self, api: &RecoilRoot) -> T { + todo!() + } -pub trait SelectionSelector { - fn select(&self, k: &K) -> CollectionSelection { - todo!() + // We use the Raw Ptr to the atom + // TODO: Make sure atoms with the same definitions don't get merged together. I don't think they do, but double check + fn static_id(self) -> u32; } -} -impl SelectionSelector for AtomFamily {} -pub trait FamilyCollection {} -impl FamilyCollection for HashMap {} -pub struct CollectionSelection { - _never: PhantomData, -} -impl Readable for CollectionSelection {} -impl Writeable for CollectionSelection {} + // Only atoms and atom families are writable + // Selectors and selector families are not + pub trait Writable: Readable + Sized { + fn use_read_write<'a>(self, ctx: Context<'a>) -> (&'a T, &'a Rc) { + todo!() + } -// ================= -// Selectors -// ================= -pub struct SelectorBuilder {} -impl SelectorBuilder { - pub fn get(&self, t: &impl Readable) -> &T { - todo!() - } -} -pub type Selector = fn(&mut SelectorBuilder) -> T; -impl Readable for Selector {} - -pub struct SelectorFamilyBuilder {} - -impl SelectorFamilyBuilder { - pub fn get(&self, t: &impl Readable) -> &T { - todo!() - } -} - -/// Create a new value as a result of a combination of previous values -/// If you need to return borrowed data, check out [`SelectorFamilyBorrowed`] -pub type SelectorFamily = fn(&mut SelectorFamilyBuilder, Key) -> Value; - -impl Readable for SelectorFamily {} - -/// Borrowed selector families are – surprisingly - discouraged. -/// This is because it's not possible safely memoize these values without keeping old versions around. -/// -/// However, it does come in handy to borrow the contents of an item without re-rendering child components. -pub type SelectorFamilyBorrowed = - for<'a> fn(&'a mut SelectorFamilyBuilder, Key) -> &'a Value; - -impl<'a, K, V: 'a> SelectionSelector for fn(&'a mut SelectorFamilyBuilder, K) -> V {} -// ================= -// API -// ================= -pub struct RecoilApi {} -impl RecoilApi { - pub fn get(&self, t: &'static Atom) -> Rc { - todo!() - } - pub fn modify(&self, t: &'static Atom, f: impl FnOnce(&mut T) -> O) -> O { - todo!() - } - pub fn set(&self, t: &'static Atom, new: T) { - self.modify(t, move |old| *old = new); - } -} - -// ================ -// Root -// ================ -type AtomId = u32; -type ConsumerId = u32; - -pub struct RecoilRoot { - consumers: RefCell>>, -} - -trait RecoilSlot {} - -impl RecoilRoot { - pub(crate) fn new() -> Self { - Self { - consumers: Default::default(), + fn use_write<'a>(self, ctx: Context<'a>) -> &'a Rc { + todo!() } } } -pub use hooks::*; +mod atoms { + use super::*; + + // Currently doesn't do anything, but will eventually add effects, id, serialize/deserialize keys, etc + // Doesn't currently allow async values, but the goal is to eventually enable them + pub struct AtomBuilder {} + + pub type Atom = fn(&mut AtomBuilder) -> T; + + // impl Readable for Atom {} + impl Readable for &'static Atom { + fn static_id(self) -> u32 { + todo!() + } + } + + impl Writable for &'static Atom {} + + mod compilests { + use super::*; + use dioxus_core::prelude::Context; + + const Example: Atom = |_| 10; + + fn test(ctx: Context) { + // ensure that atoms are both read and write + let _ = use_read(ctx, &Example); + let _ = use_read_write(ctx, &Example); + let _ = use_write(ctx, &Example); + } + } +} + +mod atomfamily { + use super::*; + pub trait FamilyCollection {} + impl FamilyCollection for HashMap {} + + pub type AtomFamily> = fn((&K, &V)) -> F; + + pub trait AtomFamilySelector { + fn select(&'static self, k: &K) -> AtomFamilySelection { + todo!() + } + } + + impl AtomFamilySelector for AtomFamily { + fn select(&'static self, k: &K) -> AtomFamilySelection { + todo!() + } + } + + pub struct AtomFamilySelection<'a, K: FamilyKey, V: AtomValue> { + root: &'static AtomFamily, + key: &'a K, + } + + impl<'a, K: FamilyKey, V: AtomValue> Readable for &AtomFamilySelection<'a, K, V> { + fn static_id(self) -> u32 { + todo!() + } + } + + impl<'a, K: FamilyKey, T: AtomValue> Writable for &AtomFamilySelection<'a, K, T> {} + + mod compiletests { + use dioxus_core::prelude::Context; + + use super::*; + const Titles: AtomFamily = |_| HashMap::new(); + + fn test(ctx: Context) { + let title = Titles.select(&10).use_read(ctx); + let t2 = use_read(ctx, &Titles.select(&10)); + } + } +} + +mod selector { + use super::*; + pub struct SelectorBuilder {} + impl SelectorBuilder { + pub fn get(&self, t: impl Readable) -> &T { + todo!() + } + } + pub type Selector = fn(&mut SelectorBuilder) -> T; + impl Readable for &'static Selector { + fn static_id(self) -> u32 { + todo!() + } + } + + pub struct SelectorFamilyBuilder {} + + impl SelectorFamilyBuilder { + pub fn get(&self, t: impl Readable) -> &T { + todo!() + } + } +} +mod selectorfamily { + use super::*; + // pub trait SelectionSelector { + // fn select(&self, k: &K) -> CollectionSelection { + // todo!() + // } + // } + // impl SelectionSelector for AtomFamily {} + + /// Create a new value as a result of a combination of previous values + /// If you need to return borrowed data, check out [`SelectorFamilyBorrowed`] + pub type SelectorFamily = fn(&mut SelectorFamilyBuilder, Key) -> Value; + + impl Readable for &'static SelectorFamily { + fn static_id(self) -> u32 { + todo!() + } + } + + /// Borrowed selector families are – surprisingly - discouraged. + /// This is because it's not possible safely memoize these values without keeping old versions around. + /// + /// However, it does come in handy to borrow the contents of an item without re-rendering child components. + pub type SelectorFamilyBorrowed = + for<'a> fn(&'a mut SelectorFamilyBuilder, Key) -> &'a Value; + + // impl<'a, K, V: 'a> SelectionSelector for fn(&'a mut SelectorFamilyBuilder, K) -> V {} +} + +mod api { + use super::*; + + // pub struct RecoilApi {} + // impl RecoilApi { + // pub fn get(&self, t: &'static Atom) -> Rc { + // todo!() + // } + // pub fn modify( + // &self, + // t: &'static Atom, + // f: impl FnOnce(&mut T) -> O, + // ) -> O { + // todo!() + // } + // pub fn set(&self, t: &'static Atom, new: T) { + // self.modify(t, move |old| *old = new); + // } + // } +} + +mod root { + use std::{ + any::{Any, TypeId}, + collections::{HashSet, VecDeque}, + iter::FromIterator, + }; + + use super::*; + // use generational_arena::Index as ConsumerId; + type AtomId = u32; + type ConsumerId = u32; + + pub struct RecoilContext { + pub(crate) inner: Rc>, + } + + impl RecoilContext { + pub fn new() -> Self { + Self { + inner: Rc::new(RefCell::new(RecoilRoot::new())), + } + } + } + + // Sometimes memoization means we don't need to re-render components that holds "correct values" + // IE we consider re-render more expensive than keeping the old value around. + // We *could* unsafely overwrite this slot, but that's just **asking** for UB (holding a &mut while & is held in components) + // + // Instead, we choose to let the hook itself hold onto the Rc by not forcing a render when T is the same. + // Whenever the component needs to be re-rendered for other reasons, the "get" method will automatically update the Rc to the most recent one. + pub struct RecoilRoot { + nodes: HashMap, + } + + struct Slot { + type_id: TypeId, + source: AtomId, + value: Rc, + consumers: HashMap>, + dependents: HashSet, + } + + impl RecoilRoot { + pub(crate) fn new() -> Self { + Self { + nodes: Default::default(), + } + } + + pub fn subscribe( + &self, + readable: impl Readable, + receiver_fn: Rc, + ) -> ConsumerId { + todo!() + } + + pub fn unsubscribe(&self, id: ConsumerId) { + todo!() + } + + /// Directly get the *slot* + /// All Atoms are held in slots (an Rc) + /// + /// + pub fn try_get_raw(&self, readable: impl Readable) -> Result> { + todo!() + } + + // pub fn try_get(&self, readable: impl Readable) -> Result<&T> { + // self.try_get_raw(readable).map(|f| f.as_ref()) + // } + + pub fn try_set( + &mut self, + writable: impl Writable, + new_val: T, + ) -> crate::error::Result<()> { + let atom_id = writable.static_id(); + + let consumers = match self.nodes.get_mut(&atom_id) { + Some(slot) => { + slot.value = Rc::new(new_val); + &slot.consumers + } + None => { + let value = Slot { + type_id: TypeId::of::(), + source: atom_id, + value: Rc::new(writable.initialize(self)), + consumers: Default::default(), + dependents: Default::default(), + }; + self.nodes.insert(atom_id, value); + &self.nodes.get(&atom_id).unwrap().consumers + } + }; + + for (_, consumer_fn) in consumers { + consumer_fn(); + } + + // if it's a an atom or selector, update all the dependents + + Ok(()) + } + + pub fn get(&self, readable: impl Readable) -> Rc { + todo!() + // self.try_get(readable).unwrap() + } + + pub fn set(&mut self, writable: impl Writable, new_val: T) { + self.try_set(writable, new_val).unwrap(); + } + + /// A slightly dangerous method to manually overwrite any slot given an AtomId + pub(crate) fn set_by_id(&self, id: AtomId, new_val: T) {} + } +} + mod hooks { use super::*; use dioxus_core::prelude::Context; - pub fn use_init_recoil_root(ctx: Context) { + pub fn use_init_recoil_root(ctx: Context, cfg: impl Fn(())) { ctx.use_create_context(move || RecoilRoot::new()) } - pub fn use_set_state<'a, T: PartialEq>( - c: Context<'a>, - t: &impl Writeable, - ) -> &'a Rc { - todo!() - } - - pub fn use_recoil_state<'a, T: PartialEq + 'static>( + /// Gain access to the recoil API directly - set, get, modify, everything + /// This is the foundational hook in which read/write/modify are built on + /// + /// This does not subscribe the component to *any* updates + /// + /// You can use this method to create controllers that perform much more complex actions than set/get + /// However, be aware that "getting" values through this hook will not subscribe the component to any updates. + pub fn use_recoil_api<'a, F: 'a>( ctx: Context<'a>, - writeable: &impl Writeable, + f: impl Fn(Rc) -> F + 'static, + ) -> &F { + let g = ctx.use_context::(); + let api = g.inner.clone(); + todo!() + } + + pub fn use_write<'a, T: AtomValue>( + ctx: Context<'a>, + writable: impl Writable, + ) -> &'a Rc { + let api = use_recoil_api(ctx, |f| f); + ctx.use_hook( + move || { + let api = api.clone(); + let raw_id = writable.static_id(); + Rc::new(move |new_val| api.set_by_id(raw_id, new_val)) as Rc + }, + move |hook| &*hook, + |hook| {}, + ) + } + + /// Read the atom and get the Rc directly to the Atom's slot + /// This is useful if you need the memoized Atom value. However, Rc is not as easy to + /// work with as + pub fn use_read_raw<'a, T: AtomValue>(ctx: Context<'a>, readable: impl Readable) -> &Rc { + struct ReadHook { + value: Rc, + consumer_id: u32, + } + + let api = use_recoil_api(ctx, |api| api); + ctx.use_hook( + move || { + let update = ctx.schedule_update(); + let val = api.try_get_raw(readable).unwrap(); + let id = api.subscribe(readable, Rc::new(update)); + ReadHook { + value: val, + consumer_id: id, + } + }, + move |hook| { + let val = api.try_get_raw(readable).unwrap(); + hook.value = val; + &hook.value + }, + |hook| { + api.unsubscribe(hook.consumer_id); + }, + ) + } + + /// + pub fn use_read<'a, T: AtomValue>(ctx: Context<'a>, readable: impl Readable) -> &'a T { + use_read_raw(ctx, readable).as_ref() + } + + /// Use an atom in both read and write modes - only available for atoms and family selections (not selectors) + /// This is equivalent to calling both `use_read` and `use_write`, but saves you the hassle and repitition + /// + /// ``` + /// const Title: Atom<&str> = |_| "hello"; + /// //... + /// let (title, set_title) = use_read_write(ctx, &Title); + /// + /// // equivalent to: + /// let (title, set_title) = (use_read(ctx, &Title), use_write(ctx, &Title)); + /// ``` + pub fn use_read_write<'a, T: AtomValue + 'static>( + ctx: Context<'a>, + writable: impl Writable, ) -> (&'a T, &'a Rc) { - todo!() + (use_read(ctx, writable), use_write(ctx, writable)) } - pub fn use_recoil_value<'a, T: PartialEq>(ctx: Context<'a>, t: &impl Readable) -> &'a T { - todo!() - } - - pub fn use_recoil_family<'a, K, V, C: FamilyCollection>( + /// Use a family collection directly + /// !! Any changes to the family will cause this subscriber to update + /// Try not to put this at the very top-level of your app. + pub fn use_read_family<'a, K, V, C: FamilyCollection>( ctx: Context<'a>, t: &AtomFamily, ) -> &'a C { todo!() } - - pub fn use_recoil_callback<'a, F: 'a>( - ctx: Context<'a>, - f: impl Fn(RecoilApi) -> F + 'static, - ) -> &F { - todo!() - } } -pub use ecs::*; mod ecs { use super::*; pub struct Blah { @@ -165,3 +450,52 @@ mod ecs { } pub type EcsModel = fn(Blah); } + +mod utils { + use super::*; + use dioxus_core::prelude::*; + + /// This tiny util wraps your main component with the initializer for the recoil root. + /// This is useful for small programs and the examples in this crate + pub fn RecoilApp( + root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree, + ) -> impl for<'a> Fn(Context<'a>, &'a T) -> DomTree { + move |ctx, props| { + use_init_recoil_root(ctx, |_| {}); + root(ctx, props) + } + } +} + +mod compiletests {} + +pub mod error { + use thiserror::Error as ThisError; + pub type Result = std::result::Result; + + #[derive(ThisError, Debug)] + pub enum Error { + #[error("Fatal Internal Error: {0}")] + FatalInternal(&'static str), + // #[error("Context is missing")] + // MissingSharedContext, + + // #[error("No event to progress")] + // NoEvent, + + // #[error("Wrong Properties Type")] + // WrongProps, + + // #[error("Base scope has not been mounted yet")] + // NotMounted, + // #[error("I/O Error: {0}")] + // BorrowMut(#[from] std::), + // #[error("eee Error: {0}")] + // IO(#[from] core::result::), + #[error("I/O Error: {0}")] + IO(#[from] std::io::Error), + + #[error(transparent)] + Other(#[from] anyhow::Error), // source and Display delegate to anyhow::Error + } +} diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml index 3b1ae961f..2a243cd88 100644 --- a/packages/ssr/Cargo.toml +++ b/packages/ssr/Cargo.toml @@ -8,3 +8,17 @@ edition = "2018" [dependencies] dioxus-core = { path = "../core", version = "0.1.0" } + + +[dev-dependencies] +tide-websockets = "*" +thiserror = "1.0.23" +log = "0.4.13" +fern = { version = "0.6.0", features = ["colored"] } +anyhow = "1.0.38" +argh = "0.1.4" +serde = "1.0.120" +serde_json = "1.0.61" +async-std = { version = "1.9.0", features = ["attributes"] } +tide = "0.16.0" +fs_extra = "1.2.0" diff --git a/packages/ssr/examples/template.html b/packages/ssr/examples/template.html new file mode 100644 index 000000000..61df94210 --- /dev/null +++ b/packages/ssr/examples/template.html @@ -0,0 +1,290 @@ + + + + + + + + + + + +

hello dioxus
+ + + + diff --git a/packages/ssr/examples/tide.rs b/packages/ssr/examples/tide.rs new file mode 100644 index 000000000..d026afc67 --- /dev/null +++ b/packages/ssr/examples/tide.rs @@ -0,0 +1,107 @@ +//! +//! +//! +use std::{borrow::Borrow, rc::Rc, sync::Arc}; + +use async_std::{prelude::*, sync::RwLock}; +use dioxus::{events::on::MouseEvent, virtual_dom::VirtualDom}; +use dioxus_core::prelude::*; +use tide::{Body, Request, Response}; +use tide_websockets::{Message, WebSocket}; + +#[derive(PartialEq, Props)] +struct ExampleProps { + initial_name: String, +} + +static Example: FC = |ctx, props| { + let dispaly_name = use_state_new(&ctx, move || props.initial_name); + + let buttons = ["Jack", "Jill", "Bob"].iter().map(|name| { + rsx!{ + button { + class: "inline-block py-4 px-8 mr-6 leading-none text-white bg-indigo-600 hover:bg-indigo-900 font-semibold rounded shadow" + onmouseover: move |_| dispaly_name.set(name.to_string()) + "{name}" + } + } + }); + + ctx.render(rsx! { + div { + class: "py-12 px-4 text-center w-full max-w-2xl mx-auto" + span { + class: "text-sm font-semibold" + "Dioxus Example: Jack and Jill" + } + h2 { + class: "text-5xl mt-2 mb-6 leading-tight font-semibold font-heading" + "Hello, {dispaly_name}" + } + } + }) +}; + +const TEMPLATE: &str = include_str!("./template.html"); + +// static VDOM: Arc> = Arc::new(RwLock::new(VirtualDom::new_with_props( +// Example, +// ExampleProps { +// initial_name: "asd".to_string(), +// }, +// ))); + +#[async_std::main] +async fn main() -> Result<(), std::io::Error> { + let mut app = tide::new(); + + let (se, re) = async_std::channel::unbounded::<()>(); + async_std::task::spawn(async move { + let dom = VirtualDom::new_with_props( + Example, + ExampleProps { + initial_name: "asd".to_string(), + }, + ); + while let Ok(msg) = re.recv().await { + // + } + }); + // app.at("/").get(|_| async { Ok(Body::from_file())}); + + app.at("/").get(|_| async { + // + let response = Response::builder(200) + .body(TEMPLATE) + .content_type(tide::http::mime::HTML) + .build(); + Ok(response) + }); + + app.at("/session/:name") + .get(WebSocket::new(|req: Request<()>, mut stream| async move { + let initial_name: String = req.param("name")?.parse().unwrap_or("...?".to_string()); + + // { + // let a = Rc::new(()); + // } + // let dom = VirtualDom::new_with_props(Example, ExampleProps { initial_name }); + + // let g = RwLock::new(Rc::new(10)); + // drop(g); + + while let Some(Ok(Message::Text(input))) = stream.next().await { + let output: String = input.chars().rev().collect(); + + stream + .send_string(format!("{} | {}", &input, &output)) + .await?; + } + + Ok(()) + })); + + app.listen("127.0.0.1:8080").await?; + + Ok(()) +} diff --git a/packages/web/examples/todomvcsingle.rs b/packages/web/examples/todomvcsingle.rs index 354c90009..39d24e56c 100644 --- a/packages/web/examples/todomvcsingle.rs +++ b/packages/web/examples/todomvcsingle.rs @@ -91,8 +91,8 @@ pub fn TodoList(ctx: Context, _props: &()) -> DomTree { }) }); - rsx! { in ctx, - div { + rsx! { in ctuse_read + div {use_read header { class: "header" h1 {"todos"} @@ -135,7 +135,7 @@ pub fn TodoEntry(ctx: Context, props: &TodoEntryProps) -> DomTree { ))} } )) -} +}use_read pub fn FilterToggles(ctx: Context, _props: &()) -> DomTree { let reducer = use_recoil_callback(ctx, |api| TodoManager(api)); diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 36efb8de0..41e0216b2 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -2,7 +2,7 @@ //! -------------- //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys. -use dioxus::prelude::Properties; +use dioxus::prelude::{Context, DomTree, Properties}; use fxhash::FxHashMap; use web_sys::{window, Document, Element, Event, Node}; // use futures::{channel::mpsc, SinkExt, StreamExt}; @@ -33,7 +33,7 @@ impl WebsysRenderer { /// /// Run the app to completion, panicing if any error occurs while rendering. /// Pairs well with the wasm_bindgen async handler - pub async fn start(root: FC<()>) { + pub async fn start(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) { Self::new(root).run().await.expect("Virtual DOM failed :("); } @@ -41,7 +41,7 @@ impl WebsysRenderer { /// /// This means that the root component must either consumes its own context, or statics are used to generate the page. /// The root component can access things like routing in its context. - pub fn new(root: FC<()>) -> Self { + pub fn new(root: impl for<'a> Fn(Context<'a>, &'a ()) -> DomTree + 'static) -> Self { Self::new_with_props(root, ()) } @@ -49,7 +49,10 @@ impl WebsysRenderer { /// Automatically progresses the creation of the VNode tree to completion. /// /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom` - pub fn new_with_props(root: FC, root_props: T) -> Self { + pub fn new_with_props( + root: impl for<'a> Fn(Context<'a>, &'a T) -> DomTree + 'static, + root_props: T, + ) -> Self { Self::from_vdom(VirtualDom::new_with_props(root, root_props)) }