Merge pull request #42 from DioxusLabs/jk/stack_dst

Jk/stack dst
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2021-12-13 00:29:11 -05:00
commit f53b824089
66 files changed
+1272 -569

No files matched your search

+2
View File
@@ -17,6 +17,7 @@ dioxus-desktop = { path = "./packages/desktop", optional = true }
dioxus-hooks = { path = "./packages/hooks", optional = true } dioxus-hooks = { path = "./packages/hooks", optional = true }
dioxus-ssr = { path = "./packages/ssr", optional = true } dioxus-ssr = { path = "./packages/ssr", optional = true }
dioxus-mobile = { path = "./packages/mobile", optional = true } dioxus-mobile = { path = "./packages/mobile", optional = true }
dioxus-liveview = { path = "./packages/liveview", optional = true }
[features] [features]
# core # core
@@ -26,6 +27,7 @@ macro = ["dioxus-core-macro"]
hooks = ["dioxus-hooks"] hooks = ["dioxus-hooks"]
html = ["dioxus-html"] html = ["dioxus-html"]
router = ["dioxus-router"] router = ["dioxus-router"]
liveview = ["dioxus-liveview"]
# utilities # utilities
atoms = [] atoms = []
+1 -1
View File
@@ -11,7 +11,7 @@ async fn main() {
dioxus::desktop::launch(App, |c| c); dioxus::desktop::launch(App, |c| c);
} }
pub static App: FC<()> = |cx, _| { pub static App: Component<()> = |cx, _| {
let count = use_state(cx, || 0); let count = use_state(cx, || 0);
let mut direction = use_state(cx, || 1); let mut direction = use_state(cx, || 1);
+1 -1
View File
@@ -13,7 +13,7 @@ fn main() {
dioxus::desktop::launch(APP, |cfg| cfg); dioxus::desktop::launch(APP, |cfg| cfg);
} }
const APP: FC<()> = |cx, _| { const APP: Component<()> = |cx, _| {
let cur_val = use_state(cx, || 0.0_f64); let cur_val = use_state(cx, || 0.0_f64);
let operator = use_state(cx, || None as Option<&'static str>); let operator = use_state(cx, || None as Option<&'static str>);
let display_value = use_state(cx, || String::from("")); let display_value = use_state(cx, || String::from(""));
+1 -1
View File
@@ -27,7 +27,7 @@ fn main() {
use dioxus::prelude::*; use dioxus::prelude::*;
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
let p1 = use_state(cx, || 0); let p1 = use_state(cx, || 0);
let p2 = use_state(cx, || 0); let p2 = use_state(cx, || 0);
+1 -1
View File
@@ -19,7 +19,7 @@ pub struct Client {
pub description: String, pub description: String,
} }
static App: FC<()> = |cx, _| { static App: Component<()> = |cx, _| {
let mut clients = use_ref(cx, || vec![] as Vec<Client>); let mut clients = use_ref(cx, || vec![] as Vec<Client>);
let mut scene = use_state(cx, || Scene::ClientsList); let mut scene = use_state(cx, || Scene::ClientsList);
+1 -1
View File
@@ -18,7 +18,7 @@ fn main() {
}); });
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
let file_manager = use_ref(cx, || Files::new()); let file_manager = use_ref(cx, || Files::new());
let files = file_manager.read(); let files = file_manager.read();
+1 -1
View File
@@ -30,7 +30,7 @@ impl Label {
} }
} }
static App: FC<()> = |cx, _props| { static App: Component<()> = |cx, _props| {
let mut items = use_ref(cx, || vec![]); let mut items = use_ref(cx, || vec![]);
let mut selected = use_state(cx, || None); let mut selected = use_state(cx, || None);
+1 -1
View File
@@ -19,7 +19,7 @@ fn main() {
dioxus::desktop::launch(App, |c| c.with_prerendered(content)); dioxus::desktop::launch(App, |c| c.with_prerendered(content));
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
let mut val = use_state(cx, || 0); let mut val = use_state(cx, || 0);
cx.render(rsx! { cx.render(rsx! {
+1 -1
View File
@@ -36,7 +36,7 @@ fn main() {
dioxus_desktop::run(APP, (), |c| c.with_edits(edits)); dioxus_desktop::run(APP, (), |c| c.with_edits(edits));
} }
const APP: FC<()> = |(cx, _props)| { const APP: Component<()> = |(cx, _props)| {
rsx!(cx, div { rsx!(cx, div {
"some app" "some app"
}) })
+1 -1
View File
@@ -31,7 +31,7 @@ fn main() {
}); });
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
let state = use_ref(cx, || Calculator::new()); let state = use_ref(cx, || Calculator::new());
let clear_display = state.read().display_value.eq("0"); let clear_display = state.read().display_value.eq("0");
+1 -1
View File
@@ -11,7 +11,7 @@ fn main() {
dioxus::desktop::launch(App, |c| c); dioxus::desktop::launch(App, |c| c);
} }
pub static App: FC<()> = |cx, _| { pub static App: Component<()> = |cx, _| {
let state = use_state(cx, PlayerState::new); let state = use_state(cx, PlayerState::new);
let is_playing = state.is_playing(); let is_playing = state.is_playing();
+1 -1
View File
@@ -7,7 +7,7 @@ fn main() {
dioxus::desktop::launch(App, |c| c); dioxus::desktop::launch(App, |c| c);
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
let mut count = use_state(cx, || 0); let mut count = use_state(cx, || 0);
cx.render(rsx! { cx.render(rsx! {
+1 -1
View File
@@ -23,7 +23,7 @@ pub enum Route {
NotFound, NotFound,
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
let route = use_router(cx, Route::parse)?; let route = use_router(cx, Route::parse)?;
cx.render(rsx! { cx.render(rsx! {
+1 -1
View File
@@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None;
use baller::Baller; use baller::Baller;
use dioxus::prelude::*; use dioxus::prelude::*;
pub static Example: FC<()> = |cx, props| { pub static Example: Component<()> = |cx, props| {
let formatting = "formatting!"; let formatting = "formatting!";
let formatting_tuple = ("a", "b"); let formatting_tuple = ("a", "b");
let lazy_fmt = format_args!("lazily formatted text"); let lazy_fmt = format_args!("lazily formatted text");
+1 -1
View File
@@ -9,7 +9,7 @@ fn main() {
println!("{}", ssr::render_vdom(&vdom, |c| c)); println!("{}", ssr::render_vdom(&vdom, |c| c));
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
cx.render(rsx!( cx.render(rsx!(
div { div {
h1 { "Title" } h1 { "Title" }
+6 -6
View File
@@ -14,7 +14,7 @@ fn main() {
const STYLE: &str = "body {overflow:hidden;}"; const STYLE: &str = "body {overflow:hidden;}";
pub static App: FC<()> = |cx, props| { pub static App: Component<()> = |cx, props| {
cx.render(rsx!( cx.render(rsx!(
div { class: "overflow-hidden" div { class: "overflow-hidden"
style { "{STYLE}" } style { "{STYLE}" }
@@ -30,7 +30,7 @@ pub static App: FC<()> = |cx, props| {
)) ))
}; };
pub static Header: FC<()> = |cx, props| { pub static Header: Component<()> = |cx, props| {
cx.render(rsx! { cx.render(rsx! {
div { div {
header { class: "text-gray-400 bg-gray-900 body-font" header { class: "text-gray-400 bg-gray-900 body-font"
@@ -56,7 +56,7 @@ pub static Header: FC<()> = |cx, props| {
}) })
}; };
pub static Hero: FC<()> = |cx, props| { pub static Hero: Component<()> = |cx, props| {
// //
cx.render(rsx! { cx.render(rsx! {
section{ class: "text-gray-400 bg-gray-900 body-font" section{ class: "text-gray-400 bg-gray-900 body-font"
@@ -94,7 +94,7 @@ pub static Hero: FC<()> = |cx, props| {
} }
}) })
}; };
pub static Entry: FC<()> = |cx, props| { pub static Entry: Component<()> = |cx, props| {
// //
cx.render(rsx! { cx.render(rsx! {
section{ class: "text-gray-400 bg-gray-900 body-font" section{ class: "text-gray-400 bg-gray-900 body-font"
@@ -107,7 +107,7 @@ pub static Entry: FC<()> = |cx, props| {
}) })
}; };
pub static StacksIcon: FC<()> = |cx, props| { pub static StacksIcon: Component<()> = |cx, props| {
cx.render(rsx!( cx.render(rsx!(
svg { svg {
// xmlns: "http://www.w3.org/2000/svg" // xmlns: "http://www.w3.org/2000/svg"
@@ -122,7 +122,7 @@ pub static StacksIcon: FC<()> = |cx, props| {
} }
)) ))
}; };
pub static RightArrowIcon: FC<()> = |cx, props| { pub static RightArrowIcon: Component<()> = |cx, props| {
cx.render(rsx!( cx.render(rsx!(
svg { svg {
fill: "none" fill: "none"
+1 -1
View File
@@ -9,7 +9,7 @@ fn main() {
dioxus::desktop::launch(App, |c| c); dioxus::desktop::launch(App, |c| c);
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
let mut count = use_state(cx, || 0); let mut count = use_state(cx, || 0);
cx.push_task(async move { cx.push_task(async move {
+1 -1
View File
@@ -22,7 +22,7 @@ pub struct TodoItem {
} }
const STYLE: &str = include_str!("./assets/todomvc.css"); const STYLE: &str = include_str!("./assets/todomvc.css");
const App: FC<()> = |cx, props| { const App: Component<()> = |cx, props| {
let mut draft = use_state(cx, || "".to_string()); let mut draft = use_state(cx, || "".to_string());
let mut todos = use_state(cx, || HashMap::<u32, Rc<TodoItem>>::new()); let mut todos = use_state(cx, || HashMap::<u32, Rc<TodoItem>>::new());
let mut filter = use_state(cx, || FilterState::All); let mut filter = use_state(cx, || FilterState::All);
+2 -2
View File
@@ -12,7 +12,7 @@ fn main() {
const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather"; const ENDPOINT: &str = "https://api.openweathermap.org/data/2.5/weather";
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
// //
let body = use_suspense( let body = use_suspense(
cx, cx,
@@ -40,7 +40,7 @@ static App: FC<()> = |cx, props| {
#[derive(PartialEq, Props)] #[derive(PartialEq, Props)]
struct WeatherProps {} struct WeatherProps {}
static WeatherDisplay: FC<WeatherProps> = |cx, props| { static WeatherDisplay: Component<WeatherProps> = |cx, props| {
// //
cx.render(rsx!( cx.render(rsx!(
div { class: "flex items-center justify-center flex-col" div { class: "flex items-center justify-center flex-col"
+1 -1
View File
@@ -19,7 +19,7 @@ fn main() {
dioxus::web::launch(App, |c| c); dioxus::web::launch(App, |c| c);
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
let mut rng = SmallRng::from_entropy(); let mut rng = SmallRng::from_entropy();
let rows = (0..1_000).map(|f| { let rows = (0..1_000).map(|f| {
let label = Label::new(&mut rng); let label = Label::new(&mut rng);
+1 -1
View File
@@ -16,7 +16,7 @@ fn main() {
dioxus::web::launch(App, |c| c); dioxus::web::launch(App, |c| c);
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
let mut count = use_state(cx, || 0); let mut count = use_state(cx, || 0);
cx.render(rsx! { cx.render(rsx! {
+1 -1
View File
@@ -23,7 +23,7 @@ criterion_group!(mbenches, create_rows);
criterion_main!(mbenches); criterion_main!(mbenches);
fn create_rows(c: &mut Criterion) { fn create_rows(c: &mut Criterion) {
static App: FC<()> = |cx, _| { static App: Component<()> = |cx, _| {
let mut rng = SmallRng::from_entropy(); let mut rng = SmallRng::from_entropy();
let rows = (0..10_000_usize).map(|f| { let rows = (0..10_000_usize).map(|f| {
let label = Label::new(&mut rng); let label = Label::new(&mut rng);
@@ -0,0 +1,34 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
fn main() {
let _ = VirtualDom::new(App);
}
fn App(cx: Context, _props: &()) -> Element {
//
cx.render(rsx!(
div {
Child {}
}
))
}
struct ChildProps<'a> {
click_handler: EventHandler<'a>,
}
fn Child(cx: Context, _props: &()) -> Element {
//
cx.render(rsx!(
div {
h1 {
"Hello, World!"
}
}
))
}
+1 -1
View File
@@ -20,7 +20,7 @@ use dioxus_html as dioxus_elements;
use rand::prelude::*; use rand::prelude::*;
fn main() { fn main() {
static App: FC<()> = |cx, _| { static App: Component<()> = |cx, _| {
let mut rng = SmallRng::from_entropy(); let mut rng = SmallRng::from_entropy();
let rows = (0..10_000_usize).map(|f| { let rows = (0..10_000_usize).map(|f| {
let label = Label::new(&mut rng); let label = Label::new(&mut rng);
+4 -2
View File
@@ -1,3 +1,5 @@
#![allow(non_snake_case)]
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_core as dioxus; use dioxus_core as dioxus;
use dioxus_core_macro::*; use dioxus_core_macro::*;
@@ -7,8 +9,8 @@ fn main() {
let _ = VirtualDom::new(Parent); let _ = VirtualDom::new(Parent);
} }
fn Parent(cx: Context, props: &()) -> Element { fn Parent(cx: Context, _props: &()) -> Element {
let value = cx.use_hook(|_| String::new(), |f| &*f); let value = cx.use_hook(|_| String::new(), |f| f);
cx.render(rsx! { cx.render(rsx! {
div { div {
+8 -8
View File
@@ -105,11 +105,11 @@ use DomEdit::*;
/// ///
/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's /// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's
/// stack machines all the way down! /// stack machines all the way down!
pub struct DiffState<'bump> { pub(crate) struct DiffState<'bump> {
scopes: &'bump ScopeArena, pub(crate) scopes: &'bump ScopeArena,
pub mutations: Mutations<'bump>, pub(crate) mutations: Mutations<'bump>,
pub(crate) stack: DiffStack<'bump>, pub(crate) stack: DiffStack<'bump>,
pub force_diff: bool, pub(crate) force_diff: bool,
} }
impl<'bump> DiffState<'bump> { impl<'bump> DiffState<'bump> {
@@ -149,7 +149,7 @@ pub(crate) enum DiffInstruction<'a> {
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum MountType<'a> { pub(crate) enum MountType<'a> {
Absorb, Absorb,
Append, Append,
Replace { old: &'a VNode<'a> }, Replace { old: &'a VNode<'a> },
@@ -159,9 +159,9 @@ pub enum MountType<'a> {
pub(crate) struct DiffStack<'bump> { pub(crate) struct DiffStack<'bump> {
pub(crate) instructions: Vec<DiffInstruction<'bump>>, pub(crate) instructions: Vec<DiffInstruction<'bump>>,
nodes_created_stack: SmallVec<[usize; 10]>, pub(crate) nodes_created_stack: SmallVec<[usize; 10]>,
pub scope_stack: SmallVec<[ScopeId; 5]>, pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
pub element_stack: SmallVec<[ElementId; 10]>, pub(crate) element_stack: SmallVec<[ElementId; 10]>,
} }
impl<'bump> DiffStack<'bump> { impl<'bump> DiffStack<'bump> {
+8 -17
View File
@@ -1,17 +1,6 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
/*
Navigating this crate:
- virtual_dom: the primary entrypoint for the crate
- scheduler: the core interior logic called by the [`VirtualDom`]
- nodes: the definition of VNodes, listeners, etc.
- diff: the stackmachine-based diffing algorithm
- hooks: foundational hooks that require crate-private APIs
- mutations: DomEdits/NodeRefs and internal API to create them
Some utilities
*/
pub(crate) mod component; pub(crate) mod component;
pub(crate) mod diff; pub(crate) mod diff;
pub(crate) mod lazynodes; pub(crate) mod lazynodes;
@@ -23,7 +12,7 @@ pub(crate) mod virtual_dom;
pub(crate) mod innerlude { pub(crate) mod innerlude {
pub use crate::component::*; pub use crate::component::*;
pub use crate::diff::*; pub(crate) use crate::diff::*;
pub use crate::lazynodes::*; pub use crate::lazynodes::*;
pub use crate::mutations::*; pub use crate::mutations::*;
pub use crate::nodes::*; pub use crate::nodes::*;
@@ -32,19 +21,21 @@ pub(crate) mod innerlude {
pub use crate::virtual_dom::*; pub use crate::virtual_dom::*;
pub type Element = Option<VPortal>; pub type Element = Option<VPortal>;
pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> Element; pub type Component<P> = for<'a> fn(Context<'a>, &'a P) -> Element;
} }
pub use crate::innerlude::{ pub use crate::innerlude::{
Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, IntoVNode, Attribute, Component, Context, DioxusElement, DomEdit, Element, ElementId, EventHandler,
LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId, EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties,
UserEvent, VElement, VFragment, VNode, VirtualDom, FC, SchedulerMsg, ScopeId, UserEvent, VElement, VFragment, VNode, VirtualDom,
}; };
pub mod prelude { pub mod prelude {
pub use crate::component::{fc_to_builder, Fragment, Properties}; pub use crate::component::{fc_to_builder, Fragment, Properties};
pub use crate::innerlude::Context; pub use crate::innerlude::Context;
pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, Scope, FC}; pub use crate::innerlude::{
Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory, Scope,
};
pub use crate::nodes::VNode; pub use crate::nodes::VNode;
pub use crate::VirtualDom; pub use crate::VirtualDom;
} }
-2
View File
@@ -16,7 +16,6 @@ pub struct Mutations<'a> {
pub edits: Vec<DomEdit<'a>>, pub edits: Vec<DomEdit<'a>>,
pub dirty_scopes: FxHashSet<ScopeId>, pub dirty_scopes: FxHashSet<ScopeId>,
pub refs: Vec<NodeRefMutation<'a>>, pub refs: Vec<NodeRefMutation<'a>>,
pub effects: Vec<&'a dyn FnMut()>,
} }
impl Debug for Mutations<'_> { impl Debug for Mutations<'_> {
@@ -113,7 +112,6 @@ impl<'a> Mutations<'a> {
edits: Vec::new(), edits: Vec::new(),
refs: Vec::new(), refs: Vec::new(),
dirty_scopes: Default::default(), dirty_scopes: Default::default(),
effects: Vec::new(),
} }
} }
+28 -9
View File
@@ -338,9 +338,32 @@ pub struct Listener<'bump> {
pub event: &'static str, pub event: &'static str,
/// The actual callback that the user specified /// The actual callback that the user specified
#[allow(clippy::type_complexity)] pub(crate) callback: EventHandler<'bump>,
pub(crate) callback: }
RefCell<Option<BumpBox<'bump, dyn FnMut(std::sync::Arc<dyn Any + Send + Sync>) + 'bump>>>,
pub struct EventHandler<'bump> {
pub callback: &'bump RefCell<Option<ListenerCallback<'bump>>>,
}
impl EventHandler<'_> {
pub fn call(&self, event: Arc<dyn Any + Send + Sync>) {
if let Some(callback) = self.callback.borrow_mut().as_mut() {
callback(event);
}
}
pub fn release(&self) {
self.callback.replace(None);
}
}
type ListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(Arc<dyn Any + Send + Sync>) + 'bump>;
impl Copy for EventHandler<'_> {}
impl Clone for EventHandler<'_> {
fn clone(&self) -> Self {
Self {
callback: self.callback,
}
}
} }
/// A cached node is a "pointer" to a "rendered" node in a particular scope /// A cached node is a "pointer" to a "rendered" node in a particular scope
@@ -608,15 +631,11 @@ impl<'a> NodeFactory<'a> {
})) }))
} }
pub fn listener( pub fn listener(self, event: &'static str, callback: EventHandler<'a>) -> Listener<'a> {
self,
event: &'static str,
callback: BumpBox<'a, dyn FnMut(Arc<dyn Any + Send + Sync>) + 'a>,
) -> Listener<'a> {
Listener { Listener {
event, event,
mounted_node: Cell::new(None), mounted_node: Cell::new(None),
callback: RefCell::new(Some(callback)), callback,
} }
} }
+50 -50
View File
@@ -7,6 +7,7 @@ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
collections::HashMap, collections::HashMap,
future::Future, future::Future,
pin::Pin,
rc::Rc, rc::Rc,
}; };
@@ -32,6 +33,14 @@ use bumpalo::{boxed::Box as BumpBox, Bump};
/// ``` /// ```
pub type Context<'a> = &'a Scope; pub type Context<'a> = &'a Scope;
/// A component's unique identifier.
///
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM and across time. ScopeIDs will never be reused
/// once a component has been unmounted.
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ScopeId(pub usize);
/// Every component in Dioxus is represented by a `Scope`. /// Every component in Dioxus is represented by a `Scope`.
/// ///
/// Scopes contain the state for hooks, the component's props, and other lifecycle information. /// Scopes contain the state for hooks, the component's props, and other lifecycle information.
@@ -44,7 +53,6 @@ pub type Context<'a> = &'a Scope;
pub struct Scope { pub struct Scope {
pub(crate) parent_scope: Option<*mut Scope>, pub(crate) parent_scope: Option<*mut Scope>,
// parent element I think?
pub(crate) container: ElementId, pub(crate) container: ElementId,
pub(crate) our_arena_idx: ScopeId, pub(crate) our_arena_idx: ScopeId,
@@ -75,17 +83,9 @@ pub struct Scope {
pub struct SelfReferentialItems<'a> { pub struct SelfReferentialItems<'a> {
pub(crate) listeners: Vec<&'a Listener<'a>>, pub(crate) listeners: Vec<&'a Listener<'a>>,
pub(crate) borrowed_props: Vec<&'a VComponent<'a>>, pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>, pub(crate) tasks: Vec<Pin<BumpBox<'a, dyn Future<Output = ()>>>>,
} }
/// A component's unique identifier.
///
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM and across time. ScopeIDs will never be reused
/// once a component has been unmounted.
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ScopeId(pub usize);
// Public methods exposed to libraries and components // Public methods exposed to libraries and components
impl Scope { impl Scope {
/// Get the subtree ID that this scope belongs to. /// Get the subtree ID that this scope belongs to.
@@ -227,11 +227,11 @@ impl Scope {
let _ = self.sender.unbounded_send(SchedulerMsg::Immediate(id)); let _ = self.sender.unbounded_send(SchedulerMsg::Immediate(id));
} }
/// Get the [`ScopeId`] of a mounted component. /// Get the Root Node of this scope
/// pub fn root_node(&self) -> &VNode {
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted. todo!("Portals have changed how we address nodes. Still fixing this, sorry.");
pub fn bump(&self) -> &Bump { // let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
&self.wip_frame().bump // unsafe { std::mem::transmute(&*node) }
} }
/// This method enables the ability to expose state to children further down the VirtualDOM Tree. /// This method enables the ability to expose state to children further down the VirtualDOM Tree.
@@ -274,6 +274,7 @@ impl Scope {
let mut search_parent = self.parent_scope; let mut search_parent = self.parent_scope;
while let Some(parent_ptr) = search_parent { while let Some(parent_ptr) = search_parent {
// safety: all parent pointers are valid thanks to the bump arena
let parent = unsafe { &*parent_ptr }; let parent = unsafe { &*parent_ptr };
if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) { if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
return Some(shared.clone().downcast::<T>().unwrap()); return Some(shared.clone().downcast::<T>().unwrap());
@@ -286,15 +287,10 @@ impl Scope {
/// Pushes the future onto the poll queue to be polled after the component renders. /// Pushes the future onto the poll queue to be polled after the component renders.
/// ///
///
///
///
/// The future is forcibly dropped if the component is not ready by the next render /// The future is forcibly dropped if the component is not ready by the next render
pub fn push_task<'src, F: Future<Output = ()>>( pub fn push_task<'src, F>(&'src self, fut: impl FnOnce() -> F + 'src) -> usize
&'src self,
fut: impl FnOnce() -> F + 'src,
) -> usize
where where
F: Future<Output = ()>,
F::Output: 'src, F::Output: 'src,
F: 'src, F: 'src,
{ {
@@ -302,18 +298,20 @@ impl Scope {
.unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx)) .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
.unwrap(); .unwrap();
// allocate the future
let fut = fut();
let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut);
// wrap it in a type that will actually drop the contents // wrap it in a type that will actually drop the contents
//
// Safety: we just made the pointer above and will promise not to alias it!
// The main reason we do this through from_raw is because Bumpalo's box does
// not support unsized coercion
let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut());
let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) }; let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) };
let pinned_fut: Pin<BumpBox<_>> = boxed_fut.into();
// erase the 'src lifetime for self-referential storage // erase the 'src lifetime for self-referential storage
let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) }; let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) };
// Push the future into the tasks
let mut items = self.items.borrow_mut(); let mut items = self.items.borrow_mut();
items.tasks.push(self_ref_fut); items.tasks.push(self_ref_fut);
items.tasks.len() - 1 items.tasks.len() - 1
} }
@@ -334,22 +332,19 @@ impl Scope {
/// } /// }
///``` ///```
pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<VPortal> { pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<VPortal> {
let frame = self.wip_frame(); let bump = &self.wip_frame().bump;
let bump = &frame.bump;
let factory = NodeFactory { bump };
let node = rsx.map(|f| f.call(factory))?;
let node = bump.alloc(node);
let node_ptr = node as *mut _; let owned_node: VNode<'src> = rsx.map(|f| f.call(NodeFactory { bump }))?;
let node_ptr = unsafe { std::mem::transmute(node_ptr) }; let alloced_vnode: &'src mut VNode<'src> = bump.alloc(owned_node);
let node_ptr: *mut VNode<'src> = alloced_vnode as *mut _;
let link = VPortal { let node: *mut VNode<'static> = unsafe { std::mem::transmute(node_ptr) };
Some(VPortal {
scope_id: Cell::new(Some(self.our_arena_idx)), scope_id: Cell::new(Some(self.our_arena_idx)),
link_idx: Cell::new(0), link_idx: Cell::new(0),
node: node_ptr, node,
}; })
Some(link)
} }
/// Store a value between renders /// Store a value between renders
@@ -379,12 +374,12 @@ impl Scope {
runner: impl FnOnce(&'src mut State) -> Output, runner: impl FnOnce(&'src mut State) -> Output,
) -> Output { ) -> Output {
let mut vals = self.hook_vals.borrow_mut(); let mut vals = self.hook_vals.borrow_mut();
let hook_len = vals.len(); let hook_len = vals.len();
let cur_idx = self.hook_idx.get(); let cur_idx = self.hook_idx.get();
if cur_idx >= hook_len { if cur_idx >= hook_len {
let val = self.hook_arena.alloc(initializer(hook_len)); vals.push(self.hook_arena.alloc(initializer(hook_len)));
vals.push(val);
} }
let state = vals let state = vals
@@ -415,6 +410,7 @@ impl Scope {
} }
} }
/// Mutable access to the "work in progress frame" - used to clear it
pub(crate) fn wip_frame_mut(&mut self) -> &mut BumpFrame { pub(crate) fn wip_frame_mut(&mut self) -> &mut BumpFrame {
match self.generation.get() & 1 == 0 { match self.generation.get() & 1 == 0 {
true => &mut self.frames[0], true => &mut self.frames[0],
@@ -422,6 +418,7 @@ impl Scope {
} }
} }
/// Access to the frame where finalized nodes existed
pub(crate) fn fin_frame(&self) -> &BumpFrame { pub(crate) fn fin_frame(&self) -> &BumpFrame {
match self.generation.get() & 1 == 1 { match self.generation.get() & 1 == 1 {
true => &self.frames[0], true => &self.frames[0],
@@ -432,20 +429,23 @@ impl Scope {
/// Reset this component's frame /// Reset this component's frame
/// ///
/// # Safety: /// # Safety:
///
/// This method breaks every reference of VNodes in the current frame. /// This method breaks every reference of VNodes in the current frame.
///
/// Calling reset itself is not usually a big deal, but we consider it important
/// due to the complex safety guarantees we need to uphold.
pub(crate) unsafe fn reset_wip_frame(&mut self) { pub(crate) unsafe fn reset_wip_frame(&mut self) {
// todo: unsafecell or something self.wip_frame_mut().bump.reset();
let bump = self.wip_frame_mut();
bump.bump.reset();
} }
/// Cycle to the next generation
pub(crate) fn cycle_frame(&self) { pub(crate) fn cycle_frame(&self) {
self.generation.set(self.generation.get() + 1); self.generation.set(self.generation.get() + 1);
} }
pub fn root_node(&self) -> &VNode { /// Get the [`Bump`] of the WIP frame.
let node = *self.wip_frame().nodes.borrow().get(0).unwrap(); pub(crate) fn bump(&self) -> &Bump {
unsafe { std::mem::transmute(&*node) } &self.wip_frame().bump
} }
} }
@@ -454,7 +454,7 @@ pub(crate) struct BumpFrame {
pub nodes: RefCell<Vec<*const VNode<'static>>>, pub nodes: RefCell<Vec<*const VNode<'static>>>,
} }
impl BumpFrame { impl BumpFrame {
pub fn new(capacity: usize) -> Self { pub(crate) fn new(capacity: usize) -> Self {
let bump = Bump::with_capacity(capacity); let bump = Bump::with_capacity(capacity);
let node = &*bump.alloc(VText { let node = &*bump.alloc(VText {
@@ -467,7 +467,7 @@ impl BumpFrame {
Self { bump, nodes } Self { bump, nodes }
} }
pub fn assign_nodelink(&self, node: &VPortal) { pub(crate) fn assign_nodelink(&self, node: &VPortal) {
let mut nodes = self.nodes.borrow_mut(); let mut nodes = self.nodes.borrow_mut();
let len = nodes.len(); let len = nodes.len();
+23 -32
View File
@@ -9,9 +9,9 @@ use std::{
use crate::innerlude::*; use crate::innerlude::*;
pub type FcSlot = *const (); pub(crate) type FcSlot = *const ();
pub struct Heuristic { pub(crate) struct Heuristic {
hook_arena_size: usize, hook_arena_size: usize,
node_arena_size: usize, node_arena_size: usize,
} }
@@ -92,15 +92,8 @@ impl ScopeArena {
let new_scope_id = ScopeId(self.scope_counter.get()); let new_scope_id = ScopeId(self.scope_counter.get());
self.scope_counter.set(self.scope_counter.get() + 1); self.scope_counter.set(self.scope_counter.get() + 1);
// log::debug!("new scope {:?} with parent {:?}", new_scope_id, container);
if let Some(old_scope) = self.free_scopes.borrow_mut().pop() { if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
let scope = unsafe { &mut *old_scope }; let scope = unsafe { &mut *old_scope };
// log::debug!(
// "reusing scope {:?} as {:?}",
// scope.our_arena_idx,
// new_scope_id
// );
scope.caller = caller; scope.caller = caller;
scope.parent_scope = parent_scope; scope.parent_scope = parent_scope;
@@ -203,8 +196,6 @@ impl ScopeArena {
pub fn try_remove(&self, id: &ScopeId) -> Option<()> { pub fn try_remove(&self, id: &ScopeId) -> Option<()> {
self.ensure_drop_safety(id); self.ensure_drop_safety(id);
// log::debug!("removing scope {:?}", id);
// Safety: // Safety:
// - ensure_drop_safety ensures that no references to this scope are in use // - ensure_drop_safety ensures that no references to this scope are in use
// - this raw pointer is removed from the map // - this raw pointer is removed from the map
@@ -281,30 +272,30 @@ impl ScopeArena {
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will /// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
/// be dropped. /// be dropped.
pub(crate) fn ensure_drop_safety(&self, scope_id: &ScopeId) { pub(crate) fn ensure_drop_safety(&self, scope_id: &ScopeId) {
let scope = self.get_scope(scope_id).unwrap(); if let Some(scope) = self.get_scope(scope_id) {
let mut items = scope.items.borrow_mut();
let mut items = scope.items.borrow_mut(); // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
// run the hooks (which hold an &mut Reference)
// recursively call ensure_drop_safety on all children
items.borrowed_props.drain(..).for_each(|comp| {
let scope_id = comp
.associated_scope
.get()
.expect("VComponents should be associated with a valid Scope");
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we self.ensure_drop_safety(&scope_id);
// run the hooks (which hold an &mut Reference)
// recursively call ensure_drop_safety on all children
items.borrowed_props.drain(..).for_each(|comp| {
let scope_id = comp
.associated_scope
.get()
.expect("VComponents should be associated with a valid Scope");
self.ensure_drop_safety(&scope_id); let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
drop_props();
});
let mut drop_props = comp.drop_props.borrow_mut().take().unwrap(); // Now that all the references are gone, we can safely drop our own references in our listeners.
drop_props(); items
}); .listeners
.drain(..)
// Now that all the references are gone, we can safely drop our own references in our listeners. .for_each(|listener| drop(listener.callback.callback.borrow_mut().take()));
items }
.listeners
.drain(..)
.for_each(|listener| drop(listener.callback.borrow_mut().take()));
} }
pub(crate) fn run_scope(&self, id: &ScopeId) -> bool { pub(crate) fn run_scope(&self, id: &ScopeId) -> bool {
@@ -375,7 +366,7 @@ impl ScopeArena {
// //
for listener in real_el.listeners.borrow().iter() { for listener in real_el.listeners.borrow().iter() {
if listener.event == event.name { if listener.event == event.name {
let mut cb = listener.callback.borrow_mut(); let mut cb = listener.callback.callback.borrow_mut();
if let Some(cb) = cb.as_mut() { if let Some(cb) = cb.as_mut() {
(cb)(event.data.clone()); (cb)(event.data.clone());
} }
+87 -22
View File
@@ -140,7 +140,7 @@ impl VirtualDom {
/// ``` /// ```
/// ///
/// Note: the VirtualDOM is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it. /// Note: the VirtualDOM is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
pub fn new(root: FC<()>) -> Self { pub fn new(root: Component<()>) -> Self {
Self::new_with_props(root, ()) Self::new_with_props(root, ())
} }
@@ -174,7 +174,7 @@ impl VirtualDom {
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" }); /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
/// let mutations = dom.rebuild(); /// let mutations = dom.rebuild();
/// ``` /// ```
pub fn new_with_props<P: 'static>(root: FC<P>, root_props: P) -> Self { pub fn new_with_props<P: 'static>(root: Component<P>, root_props: P) -> Self {
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>(); let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
Self::new_with_props_and_scheduler(root, root_props, sender, receiver) Self::new_with_props_and_scheduler(root, root_props, sender, receiver)
} }
@@ -183,8 +183,13 @@ impl VirtualDom {
/// ///
/// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the /// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
/// VirtualDom to be created just to retrieve its channel receiver. /// VirtualDom to be created just to retrieve its channel receiver.
///
/// ```rust
/// let (sender, receiver) = futures_channel::mpsc::unbounded();
/// let dom = VirtualDom::new_with_scheduler(Example, (), sender, receiver);
/// ```
pub fn new_with_props_and_scheduler<P: 'static>( pub fn new_with_props_and_scheduler<P: 'static>(
root: FC<P>, root: Component<P>,
root_props: P, root_props: P,
sender: UnboundedSender<SchedulerMsg>, sender: UnboundedSender<SchedulerMsg>,
receiver: UnboundedReceiver<SchedulerMsg>, receiver: UnboundedReceiver<SchedulerMsg>,
@@ -235,27 +240,55 @@ impl VirtualDom {
/// # Example /// # Example
/// ///
/// ```rust, ignore /// ```rust, ignore
/// /// let dom = VirtualDom::new(App);
/// /// let sender = dom.get_scheduler_channel();
/// ``` /// ```
pub fn get_scheduler_channel(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> { pub fn get_scheduler_channel(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
self.sender.clone() self.sender.clone()
} }
/// Add a new message to the scheduler queue directly.
///
///
/// This method makes it possible to send messages to the scheduler from outside the VirtualDom without having to
/// call `get_schedule_channel` and then `send`.
///
/// # Example
/// ```rust, ignore
/// let dom = VirtualDom::new(App);
/// dom.insert_scheduler_message(SchedulerMsg::Immediate(ScopeId(0)));
/// ```
pub fn insert_scheduler_message(&self, msg: SchedulerMsg) {
self.sender.unbounded_send(msg).unwrap()
}
/// Check if the [`VirtualDom`] has any pending updates or work to be done. /// Check if the [`VirtualDom`] has any pending updates or work to be done.
/// ///
/// # Example /// # Example
/// ///
/// ```rust, ignore /// ```rust, ignore
/// let dom = VirtualDom::new(App);
/// ///
/// /// // the dom is "dirty" when it is started and must be rebuilt to get the first render
/// assert!(dom.has_any_work());
/// ``` /// ```
pub fn has_any_work(&self) -> bool { pub fn has_work(&self) -> bool {
!(self.dirty_scopes.is_empty() && self.pending_messages.is_empty()) !(self.dirty_scopes.is_empty() && self.pending_messages.is_empty())
} }
/// Waits for the scheduler to have work /// Wait for the scheduler to have any work.
///
/// This method polls the internal future queue *and* the scheduler channel.
/// To add work to the VirtualDom, insert a message via the scheduler channel.
///
/// This lets us poll async tasks during idle periods without blocking the main thread. /// This lets us poll async tasks during idle periods without blocking the main thread.
///
/// # Example
///
/// ```rust, ignore
/// let dom = VirtualDom::new(App);
/// let sender = dom.get_scheduler_channel();
/// ```
pub async fn wait_for_work(&mut self) { pub async fn wait_for_work(&mut self) {
loop { loop {
if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() { if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() {
@@ -390,9 +423,10 @@ impl VirtualDom {
committed_mutations.push(mutations); committed_mutations.push(mutations);
} else { } else {
log::debug!("Could not finish work in time");
// leave the work in an incomplete state // leave the work in an incomplete state
//
// todo: we should store the edits and re-apply them later
// for now, we just dump the work completely (threadsafe)
return committed_mutations; return committed_mutations;
} }
} }
@@ -400,7 +434,7 @@ impl VirtualDom {
committed_mutations committed_mutations
} }
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
/// ///
/// The diff machine expects the RealDom's stack to be the root of the application. /// The diff machine expects the RealDom's stack to be the root of the application.
/// ///
@@ -475,6 +509,15 @@ impl VirtualDom {
/// Renders an `rsx` call into the Base Scope's allocator. /// Renders an `rsx` call into the Base Scope's allocator.
/// ///
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test. /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
///
/// ```rust
/// fn Base(cx: Context, props: &()) -> Element {
/// rsx!(cx, div {})
/// }
///
/// let dom = VirtualDom::new(Base);
/// let nodes = dom.render_nodes(rsx!("div"));
/// ```
pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> &'a VNode<'a> { pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> &'a VNode<'a> {
let scope = self.scopes.get_scope(&self.base_scope).unwrap(); let scope = self.scopes.get_scope(&self.base_scope).unwrap();
let frame = scope.wip_frame(); let frame = scope.wip_frame();
@@ -485,7 +528,16 @@ impl VirtualDom {
/// Renders an `rsx` call into the Base Scope's allocator. /// Renders an `rsx` call into the Base Scope's allocator.
/// ///
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test. /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
///
/// ```rust
/// fn Base(cx: Context, props: &()) -> Element {
/// rsx!(cx, div {})
/// }
///
/// let dom = VirtualDom::new(Base);
/// let nodes = dom.render_nodes(rsx!("div"));
/// ```
pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
let mut machine = DiffState::new(&self.scopes); let mut machine = DiffState::new(&self.scopes);
machine.stack.push(DiffInstruction::Diff { new, old }); machine.stack.push(DiffInstruction::Diff { new, old });
@@ -498,6 +550,16 @@ impl VirtualDom {
/// Renders an `rsx` call into the Base Scope's allocator. /// Renders an `rsx` call into the Base Scope's allocator.
/// ///
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test. /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
///
///
/// ```rust
/// fn Base(cx: Context, props: &()) -> Element {
/// rsx!(cx, div {})
/// }
///
/// let dom = VirtualDom::new(Base);
/// let nodes = dom.render_nodes(rsx!("div"));
/// ```
pub fn create_vnodes<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> { pub fn create_vnodes<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
let nodes = self.render_vnodes(left); let nodes = self.render_vnodes(left);
let mut machine = DiffState::new(&self.scopes); let mut machine = DiffState::new(&self.scopes);
@@ -507,9 +569,19 @@ impl VirtualDom {
machine.mutations machine.mutations
} }
/// Renders an `rsx` call into the Base Scope's allocator. /// Renders an `rsx` call into the Base Scopes's arena.
/// ///
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test. /// Useful when needing to diff two rsx! calls from outside the VirtualDom, such as in a test.
///
///
/// ```rust
/// fn Base(cx: Context, props: &()) -> Element {
/// rsx!(cx, div {})
/// }
///
/// let dom = VirtualDom::new(Base);
/// let nodes = dom.render_nodes(rsx!("div"));
/// ```
pub fn diff_lazynodes<'a>( pub fn diff_lazynodes<'a>(
&'a self, &'a self,
left: Option<LazyNodes<'a, '_>>, left: Option<LazyNodes<'a, '_>>,
@@ -666,14 +738,7 @@ impl<'a> Future for PollTasks<'a> {
// really this should just be retain_mut but that doesn't exist yet // really this should just be retain_mut but that doesn't exist yet
while let Some(mut task) = items.tasks.pop() { while let Some(mut task) = items.tasks.pop() {
// todo: does this make sense? if task.as_mut().poll(cx).is_ready() {
// I don't usually write futures by hand
// I think the futures neeed to be pinned using bumpbox or something
// right now, they're bump allocated so this shouldn't matter anyway - they're not going to move
let task_mut = task.as_mut();
let pinned = unsafe { Pin::new_unchecked(task_mut) };
if pinned.poll(cx).is_ready() {
all_pending = false all_pending = false
} else { } else {
unfinished_tasks.push(task); unfinished_tasks.push(task);
+1 -1
View File
@@ -10,7 +10,7 @@ fn test_borrowed_state() {
let _ = VirtualDom::new(Parent); let _ = VirtualDom::new(Parent);
} }
fn Parent(cx: Context, props: &()) -> Element { fn Parent(cx: Context, _props: &()) -> Element {
let value = cx.use_hook(|_| String::new(), |f| &*f); let value = cx.use_hook(|_| String::new(), |f| &*f);
cx.render(rsx! { cx.render(rsx! {
+8 -8
View File
@@ -1,4 +1,4 @@
#![allow(unused, non_upper_case_globals)] #![allow(unused, non_upper_case_globals, non_snake_case)]
//! Prove that the dom works normally through virtualdom methods. //! Prove that the dom works normally through virtualdom methods.
//! //!
@@ -13,7 +13,7 @@ use dioxus_html as dioxus_elements;
mod test_logging; mod test_logging;
use DomEdit::*; use DomEdit::*;
fn new_dom<P: 'static + Send>(app: FC<P>, props: P) -> VirtualDom { fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
const IS_LOGGING_ENABLED: bool = false; const IS_LOGGING_ENABLED: bool = false;
test_logging::set_up_logging(IS_LOGGING_ENABLED); test_logging::set_up_logging(IS_LOGGING_ENABLED);
VirtualDom::new_with_props(app, props) VirtualDom::new_with_props(app, props)
@@ -21,7 +21,7 @@ fn new_dom<P: 'static + Send>(app: FC<P>, props: P) -> VirtualDom {
#[test] #[test]
fn test_original_diff() { fn test_original_diff() {
static APP: FC<()> = |cx, props| { static APP: Component<()> = |cx, props| {
cx.render(rsx! { cx.render(rsx! {
div { div {
div { div {
@@ -57,7 +57,7 @@ fn test_original_diff() {
#[test] #[test]
fn create() { fn create() {
static APP: FC<()> = |cx, props| { static APP: Component<()> = |cx, props| {
cx.render(rsx! { cx.render(rsx! {
div { div {
div { div {
@@ -120,7 +120,7 @@ fn create() {
#[test] #[test]
fn create_list() { fn create_list() {
static APP: FC<()> = |cx, props| { static APP: Component<()> = |cx, props| {
cx.render(rsx! { cx.render(rsx! {
{(0..3).map(|f| rsx!{ div { {(0..3).map(|f| rsx!{ div {
"hello" "hello"
@@ -169,7 +169,7 @@ fn create_list() {
#[test] #[test]
fn create_simple() { fn create_simple() {
static APP: FC<()> = |cx, props| { static APP: Component<()> = |cx, props| {
cx.render(rsx! { cx.render(rsx! {
div {} div {}
div {} div {}
@@ -207,7 +207,7 @@ fn create_simple() {
} }
#[test] #[test]
fn create_components() { fn create_components() {
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
cx.render(rsx! { cx.render(rsx! {
Child { "abc1" } Child { "abc1" }
Child { "abc2" } Child { "abc2" }
@@ -273,7 +273,7 @@ fn create_components() {
} }
#[test] #[test]
fn anchors() { fn anchors() {
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
cx.render(rsx! { cx.render(rsx! {
{true.then(|| rsx!{ div { "hello" } })} {true.then(|| rsx!{ div { "hello" } })}
{false.then(|| rsx!{ div { "goodbye" } })} {false.then(|| rsx!{ div { "goodbye" } })}
+10 -10
View File
@@ -22,7 +22,7 @@ fn manual_diffing() {
value: Shared<&'static str>, value: Shared<&'static str>,
} }
static App: FC<AppProps> = |cx, props| { static App: Component<AppProps> = |cx, props| {
let val = props.value.lock().unwrap(); let val = props.value.lock().unwrap();
cx.render(rsx! { div { "{val}" } }) cx.render(rsx! { div { "{val}" } })
}; };
@@ -46,7 +46,7 @@ fn manual_diffing() {
#[test] #[test]
fn events_generate() { fn events_generate() {
static App: FC<()> = |cx, _| { static App: Component<()> = |cx, _| {
let mut count = use_state(cx, || 0); let mut count = use_state(cx, || 0);
let inner = match *count { let inner = match *count {
@@ -69,7 +69,7 @@ fn events_generate() {
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
let mut channel = dom.get_scheduler_channel(); let mut channel = dom.get_scheduler_channel();
assert!(dom.has_any_work()); assert!(dom.has_work());
let edits = dom.rebuild(); let edits = dom.rebuild();
assert_eq!( assert_eq!(
@@ -105,7 +105,7 @@ fn events_generate() {
#[test] #[test]
fn components_generate() { fn components_generate() {
static App: FC<()> = |cx, _| { static App: Component<()> = |cx, _| {
let mut render_phase = use_state(cx, || 0); let mut render_phase = use_state(cx, || 0);
render_phase += 1; render_phase += 1;
@@ -122,7 +122,7 @@ fn components_generate() {
}) })
}; };
static Child: FC<()> = |cx, _| { static Child: Component<()> = |cx, _| {
cx.render(rsx! { cx.render(rsx! {
h1 {} h1 {}
}) })
@@ -222,7 +222,7 @@ fn components_generate() {
#[test] #[test]
fn component_swap() { fn component_swap() {
// simple_logger::init(); // simple_logger::init();
static App: FC<()> = |cx, _| { static App: Component<()> = |cx, _| {
let mut render_phase = use_state(cx, || 0); let mut render_phase = use_state(cx, || 0);
render_phase += 1; render_phase += 1;
@@ -261,7 +261,7 @@ fn component_swap() {
}) })
}; };
static NavBar: FC<()> = |cx, _| { static NavBar: Component<()> = |cx, _| {
println!("running navbar"); println!("running navbar");
cx.render(rsx! { cx.render(rsx! {
h1 { h1 {
@@ -271,7 +271,7 @@ fn component_swap() {
}) })
}; };
static NavLink: FC<()> = |cx, _| { static NavLink: Component<()> = |cx, _| {
println!("running navlink"); println!("running navlink");
cx.render(rsx! { cx.render(rsx! {
h1 { h1 {
@@ -280,7 +280,7 @@ fn component_swap() {
}) })
}; };
static Dashboard: FC<()> = |cx, _| { static Dashboard: Component<()> = |cx, _| {
println!("running dashboard"); println!("running dashboard");
cx.render(rsx! { cx.render(rsx! {
div { div {
@@ -289,7 +289,7 @@ fn component_swap() {
}) })
}; };
static Results: FC<()> = |cx, _| { static Results: Component<()> = |cx, _| {
println!("running results"); println!("running results");
cx.render(rsx! { cx.render(rsx! {
div { div {
+2 -2
View File
@@ -13,12 +13,12 @@ mod test_logging;
fn shared_state_test() { fn shared_state_test() {
struct MySharedState(&'static str); struct MySharedState(&'static str);
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
cx.provide_state(MySharedState("world!")); cx.provide_state(MySharedState("world!"));
cx.render(rsx!(Child {})) cx.render(rsx!(Child {}))
}; };
static Child: FC<()> = |cx, props| { static Child: Component<()> = |cx, props| {
let shared = cx.consume_state::<MySharedState>()?; let shared = cx.consume_state::<MySharedState>()?;
cx.render(rsx!("Hello, {shared.0}")) cx.render(rsx!("Hello, {shared.0}"))
}; };
+6 -6
View File
@@ -19,7 +19,7 @@ use DomEdit::*;
#[test] #[test]
fn app_runs() { fn app_runs() {
static App: FC<()> = |cx, props| rsx!(cx, div{"hello"} ); static App: Component<()> = |cx, props| rsx!(cx, div{"hello"} );
let mut vdom = VirtualDom::new(App); let mut vdom = VirtualDom::new(App);
let edits = vdom.rebuild(); let edits = vdom.rebuild();
@@ -27,7 +27,7 @@ fn app_runs() {
#[test] #[test]
fn fragments_work() { fn fragments_work() {
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
cx.render(rsx!( cx.render(rsx!(
div{"hello"} div{"hello"}
div{"goodbye"} div{"goodbye"}
@@ -41,7 +41,7 @@ fn fragments_work() {
#[test] #[test]
fn lists_work() { fn lists_work() {
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
cx.render(rsx!( cx.render(rsx!(
h1 {"hello"} h1 {"hello"}
{(0..6).map(|f| rsx!(span{ "{f}" }))} {(0..6).map(|f| rsx!(span{ "{f}" }))}
@@ -54,7 +54,7 @@ fn lists_work() {
#[test] #[test]
fn conditional_rendering() { fn conditional_rendering() {
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
cx.render(rsx!( cx.render(rsx!(
h1 {"hello"} h1 {"hello"}
{true.then(|| rsx!(span{ "a" }))} {true.then(|| rsx!(span{ "a" }))}
@@ -87,13 +87,13 @@ fn conditional_rendering() {
#[test] #[test]
fn child_components() { fn child_components() {
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
cx.render(rsx!( cx.render(rsx!(
{true.then(|| rsx!(Child { }))} {true.then(|| rsx!(Child { }))}
{false.then(|| rsx!(Child { }))} {false.then(|| rsx!(Child { }))}
)) ))
}; };
static Child: FC<()> = |cx, props| { static Child: Component<()> = |cx, props| {
cx.render(rsx!( cx.render(rsx!(
h1 {"hello"} h1 {"hello"}
h1 {"goodbye"} h1 {"goodbye"}
+1 -1
View File
@@ -15,7 +15,7 @@ fn main() {
dioxus_desktop::launch(App, |c| c); dioxus_desktop::launch(App, |c| c);
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
let mut count = use_state(cx, || 0); let mut count = use_state(cx, || 0);
log::debug!("count is {:?}", count); log::debug!("count is {:?}", count);
+4 -6
View File
@@ -15,7 +15,6 @@ use std::{
sync::atomic::AtomicBool, sync::atomic::AtomicBool,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
}; };
use tokio::task::LocalSet;
use wry::{ use wry::{
application::{ application::{
accelerator::{Accelerator, SysMods}, accelerator::{Accelerator, SysMods},
@@ -30,14 +29,14 @@ use wry::{
}; };
pub fn launch( pub fn launch(
root: FC<()>, root: Component<()>,
config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) { ) {
launch_with_props(root, (), config_builder) launch_with_props(root, (), config_builder)
} }
pub fn launch_with_props<P: Properties + 'static + Send + Sync>( pub fn launch_with_props<P: Properties + 'static + Send + Sync>(
root: FC<P>, root: Component<P>,
props: P, props: P,
builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) { ) {
@@ -51,7 +50,7 @@ struct Response<'a> {
} }
pub fn run<T: 'static + Send + Sync>( pub fn run<T: 'static + Send + Sync>(
root: FC<T>, root: Component<T>,
props: T, props: T,
user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>, user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) { ) {
@@ -112,7 +111,7 @@ pub struct DesktopController {
impl DesktopController { impl DesktopController {
// Launch the virtualdom on its own thread managed by tokio // Launch the virtualdom on its own thread managed by tokio
// returns the desktop state // returns the desktop state
pub fn new_on_tokio<P: Send + 'static>(root: FC<P>, props: P) -> Self { pub fn new_on_tokio<P: Send + 'static>(root: Component<P>, props: P) -> Self {
let edit_queue = Arc::new(RwLock::new(VecDeque::new())); let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
let pending_edits = edit_queue.clone(); let pending_edits = edit_queue.clone();
@@ -142,7 +141,6 @@ impl DesktopController {
dom.wait_for_work().await; dom.wait_for_work().await;
let mut muts = dom.work_with_deadline(|| false); let mut muts = dom.work_with_deadline(|| false);
while let Some(edit) = muts.pop() { while let Some(edit) = muts.pop() {
log::debug!("found mutations {:?}", muts);
edit_queue edit_queue
.write() .write()
.unwrap() .unwrap()
+6
View File
@@ -1,6 +1,9 @@
mod usestate; mod usestate;
pub use usestate::{use_state, AsyncUseState, UseState}; pub use usestate::{use_state, AsyncUseState, UseState};
mod usestate2;
// pub use usestate2::use_state2;
mod useref; mod useref;
pub use useref::*; pub use useref::*;
@@ -9,3 +12,6 @@ pub use use_shared_state::*;
mod usecoroutine; mod usecoroutine;
pub use usecoroutine::*; pub use usecoroutine::*;
mod usemodel;
pub use usemodel::*;
+17 -5
View File
@@ -8,7 +8,7 @@ use std::{
type ProvidedState<T> = RefCell<ProvidedStateInner<T>>; type ProvidedState<T> = RefCell<ProvidedStateInner<T>>;
// Tracks all the subscribers to a shared State // Tracks all the subscribers to a shared State
pub(crate) struct ProvidedStateInner<T> { pub struct ProvidedStateInner<T> {
value: Rc<RefCell<T>>, value: Rc<RefCell<T>>,
notify_any: Rc<dyn Fn(ScopeId)>, notify_any: Rc<dyn Fn(ScopeId)>,
consumers: HashSet<ScopeId>, consumers: HashSet<ScopeId>,
@@ -20,6 +20,14 @@ impl<T> ProvidedStateInner<T> {
(self.notify_any)(*consumer); (self.notify_any)(*consumer);
} }
} }
pub fn write(&self) -> RefMut<T> {
self.value.borrow_mut()
}
pub fn read(&self) -> Ref<T> {
self.value.borrow()
}
} }
/// This hook provides some relatively light ergonomics around shared state. /// This hook provides some relatively light ergonomics around shared state.
@@ -114,10 +122,10 @@ impl<'a, T: 'static> UseSharedState<'a, T> {
} }
pub fn notify_consumers(self) { pub fn notify_consumers(self) {
// if !self.needs_notification.get() { if !self.needs_notification.get() {
self.root.borrow_mut().notify_consumers(); self.root.borrow_mut().notify_consumers();
// self.needs_notification.set(true); self.needs_notification.set(true);
// } }
} }
pub fn read_write(&self) -> (Ref<'_, T>, &Self) { pub fn read_write(&self) -> (Ref<'_, T>, &Self) {
@@ -138,6 +146,10 @@ impl<'a, T: 'static> UseSharedState<'a, T> {
pub fn write_silent(&self) -> RefMut<'_, T> { pub fn write_silent(&self) -> RefMut<'_, T> {
self.value.borrow_mut() self.value.borrow_mut()
} }
pub fn inner(&self) -> Rc<RefCell<ProvidedStateInner<T>>> {
self.root.clone()
}
} }
impl<T> Copy for UseSharedState<'_, T> {} impl<T> Copy for UseSharedState<'_, T> {}
+17 -3
View File
@@ -6,9 +6,9 @@ use std::{
rc::Rc, rc::Rc,
}; };
pub fn use_coroutine<'a, F: Future<Output = ()> + 'a>( pub fn use_coroutine<'a, F: Future<Output = ()> + 'static>(
cx: Context<'a>, cx: Context<'a>,
f: impl FnOnce() -> F + 'a, mut f: impl FnMut() -> F + 'a,
) -> CoroutineHandle { ) -> CoroutineHandle {
// //
cx.use_hook( cx.use_hook(
@@ -59,10 +59,19 @@ pub struct CoroutineHandle<'a> {
cx: Context<'a>, cx: Context<'a>,
inner: &'a State, inner: &'a State,
} }
impl Clone for CoroutineHandle<'_> {
fn clone(&self) -> Self {
CoroutineHandle {
cx: self.cx,
inner: self.inner,
}
}
}
impl Copy for CoroutineHandle<'_> {}
impl<'a> CoroutineHandle<'a> { impl<'a> CoroutineHandle<'a> {
pub fn start(&self) { pub fn start(&self) {
if self.inner.running.get() { if self.is_running() {
return; return;
} }
if let Some(submit) = self.inner.submit.borrow_mut().take() { if let Some(submit) = self.inner.submit.borrow_mut().take() {
@@ -71,5 +80,10 @@ impl<'a> CoroutineHandle<'a> {
self.cx.push_task(|| fut.as_mut().unwrap().as_mut()); self.cx.push_task(|| fut.as_mut().unwrap().as_mut());
} }
} }
pub fn is_running(&self) -> bool {
self.inner.running.get()
}
pub fn resume(&self) {} pub fn resume(&self) {}
} }
+136
View File
@@ -0,0 +1,136 @@
//! When building complex components, it's occasionally useful to dip into a pure MVC pattern instead of the
//! React hooks pattern. Hooks are useful to abstract over some reusable logic, but many models are not reusable
//! in the same way that hooks are.
//!
//! In these cases, we provide `use_model` - a convenient way of abstracting over some state and async functions.
use dioxus_core::prelude::Context;
use futures::Future;
use std::{
cell::{Cell, Ref, RefCell, RefMut},
marker::PhantomData,
pin::Pin,
rc::Rc,
};
pub fn use_model<T: 'static>(cx: Context, f: impl FnOnce() -> T) -> UseModel<T> {
cx.use_hook(
|_| UseModelInner {
update_scheduled: Cell::new(false),
update_callback: cx.schedule_update(),
value: RefCell::new(f()),
// tasks: RefCell::new(Vec::new()),
},
|inner| {
inner.update_scheduled.set(false);
UseModel { inner }
},
)
}
pub struct UseModel<'a, T> {
inner: &'a UseModelInner<T>,
}
struct UseModelInner<T> {
update_scheduled: Cell<bool>,
update_callback: Rc<dyn Fn()>,
value: RefCell<T>,
// tasks: RefCell<Vec<ModelTask>>,
}
type ModelTask = Pin<Box<dyn Future<Output = ()> + 'static>>;
impl<'a, T: 'static> UseModel<'a, T> {
pub fn read(&self) -> Ref<'_, T> {
self.inner.value.borrow()
}
pub fn write(&self) -> RefMut<'_, T> {
self.needs_update();
self.inner.value.borrow_mut()
}
/// Allows the ability to write the value without forcing a re-render
pub fn write_silent(&self) -> RefMut<'_, T> {
self.inner.value.borrow_mut()
}
pub fn needs_update(&self) {
if !self.inner.update_scheduled.get() {
self.inner.update_scheduled.set(true);
(self.inner.update_callback)();
}
}
pub fn set(&self, new: T) {
*self.inner.value.borrow_mut() = new;
self.needs_update();
}
pub fn read_write(&self) -> (Ref<'_, T>, &Self) {
(self.read(), self)
}
pub fn start(&self, f: impl FnOnce() -> ModelTask) {
todo!()
}
}
// keep a coroutine going
pub fn use_model_coroutine<T, F: Future<Output = ()> + 'static>(
cx: Context,
model: UseModel<T>,
f: impl FnOnce(AppModels) -> F,
) -> UseModelCoroutine {
cx.use_hook(
|_| {
//
UseModelTaskInner {
task: Default::default(),
}
},
|inner| {
if let Some(task) = inner.task.get_mut() {
cx.push_task(|| task);
}
//
todo!()
},
)
}
pub struct UseModelCoroutine {}
struct UseModelTaskInner {
task: RefCell<Option<ModelTask>>,
}
impl UseModelCoroutine {
pub fn start(&self) {}
}
pub struct ModelAsync<T> {
_p: PhantomData<T>,
}
impl<T> ModelAsync<T> {
pub fn write(&self) -> RefMut<'_, T> {
todo!()
}
pub fn read(&self) -> Ref<'_, T> {
todo!()
}
}
pub struct AppModels {}
impl AppModels {
pub fn get<T: 'static>(&self) -> ModelAsync<T> {
unimplemented!()
}
}
impl<T> Copy for UseModel<'_, T> {}
impl<'a, T> Clone for UseModel<'a, T> {
fn clone(&self) -> Self {
Self { inner: self.inner }
}
}
+5 -5
View File
@@ -8,12 +8,12 @@ use dioxus_core::Context;
pub fn use_ref<T: 'static>(cx: Context, f: impl FnOnce() -> T) -> UseRef<T> { pub fn use_ref<T: 'static>(cx: Context, f: impl FnOnce() -> T) -> UseRef<T> {
cx.use_hook( cx.use_hook(
|_| UseRefInner { |_| UseRefInner {
update_scheuled: Cell::new(false), update_scheduled: Cell::new(false),
update_callback: cx.schedule_update(), update_callback: cx.schedule_update(),
value: RefCell::new(f()), value: RefCell::new(f()),
}, },
|inner| { |inner| {
inner.update_scheuled.set(false); inner.update_scheduled.set(false);
UseRef { inner } UseRef { inner }
}, },
) )
@@ -23,7 +23,7 @@ pub struct UseRef<'a, T> {
inner: &'a UseRefInner<T>, inner: &'a UseRefInner<T>,
} }
struct UseRefInner<T> { struct UseRefInner<T> {
update_scheuled: Cell<bool>, update_scheduled: Cell<bool>,
update_callback: Rc<dyn Fn()>, update_callback: Rc<dyn Fn()>,
value: RefCell<T>, value: RefCell<T>,
} }
@@ -54,8 +54,8 @@ impl<'a, T> UseRef<'a, T> {
} }
pub fn needs_update(&self) { pub fn needs_update(&self) {
if !self.inner.update_scheuled.get() { if !self.inner.update_scheduled.get() {
self.inner.update_scheuled.set(true); self.inner.update_scheduled.set(true);
(self.inner.update_callback)(); (self.inner.update_callback)();
} }
} }
+34 -7
View File
@@ -8,7 +8,7 @@ use std::{
/// Store state between component renders! /// Store state between component renders!
/// ///
/// ## The "Pinnacle" of state hooks /// ## Dioxus equivalent of useState, designed for Rust
/// ///
/// The Dioxus version of `useState` is the "king daddy" of state management. It allows you to ergonomically store and /// The Dioxus version of `useState` is the "king daddy" of state management. It allows you to ergonomically store and
/// modify state between component renders. When the state is updated, the component will re-render. /// modify state between component renders. When the state is updated, the component will re-render.
@@ -55,9 +55,9 @@ pub fn use_state<'a, T: 'static>(
) -> UseState<'a, T> { ) -> UseState<'a, T> {
cx.use_hook( cx.use_hook(
move |_| { move |_| {
// let first_val = initial_state_fn();
UseStateInner { UseStateInner {
current_val: initial_state_fn(), current_val: Rc::new(first_val),
update_callback: cx.schedule_update(), update_callback: cx.schedule_update(),
wip: Rc::new(RefCell::new(None)), wip: Rc::new(RefCell::new(None)),
update_scheuled: Cell::new(false), update_scheuled: Cell::new(false),
@@ -65,9 +65,15 @@ pub fn use_state<'a, T: 'static>(
}, },
move |hook| { move |hook| {
hook.update_scheuled.set(false); hook.update_scheuled.set(false);
let mut new_val = hook.wip.borrow_mut(); let mut new_val = hook.wip.borrow_mut();
if new_val.is_some() { if new_val.is_some() {
hook.current_val = new_val.take().unwrap(); // if there's only one reference (weak or otherwise), we can just swap the values
if let Some(val) = Rc::get_mut(&mut hook.current_val) {
*val = new_val.take().unwrap();
} else {
hook.current_val = Rc::new(new_val.take().unwrap());
}
} }
UseState { inner: &*hook } UseState { inner: &*hook }
@@ -75,7 +81,7 @@ pub fn use_state<'a, T: 'static>(
) )
} }
struct UseStateInner<T: 'static> { struct UseStateInner<T: 'static> {
current_val: T, current_val: Rc<T>,
update_scheuled: Cell<bool>, update_scheuled: Cell<bool>,
update_callback: Rc<dyn Fn()>, update_callback: Rc<dyn Fn()>,
wip: Rc<RefCell<Option<T>>>, wip: Rc<RefCell<Option<T>>>,
@@ -117,6 +123,10 @@ impl<'a, T: 'static> UseState<'a, T> {
&self.inner.current_val &self.inner.current_val
} }
pub fn get_rc(&self) -> &'a Rc<T> {
&self.inner.current_val
}
/// Get the current status of the work-in-progress data /// Get the current status of the work-in-progress data
pub fn get_wip(&self) -> Ref<Option<T>> { pub fn get_wip(&self) -> Ref<Option<T>> {
self.inner.wip.borrow() self.inner.wip.borrow()
@@ -134,14 +144,15 @@ impl<'a, T: 'static> UseState<'a, T> {
pub fn setter(&self) -> Rc<dyn Fn(T)> { pub fn setter(&self) -> Rc<dyn Fn(T)> {
let slot = self.inner.wip.clone(); let slot = self.inner.wip.clone();
Rc::new(move |new| { Rc::new(move |new| {
//
*slot.borrow_mut() = Some(new); *slot.borrow_mut() = Some(new);
}) })
} }
pub fn for_async(&self) -> AsyncUseState<T> { pub fn for_async(&self) -> AsyncUseState<T> {
AsyncUseState { AsyncUseState {
re_render: self.inner.update_callback.clone(),
wip: self.inner.wip.clone(), wip: self.inner.wip.clone(),
inner: self.inner.current_val.clone(),
} }
} }
@@ -164,11 +175,15 @@ impl<'a, T: 'static + ToOwned<Owned = T>> UseState<'a, T> {
// "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this // "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
RefMut::map(self.inner.wip.borrow_mut(), |slot| { RefMut::map(self.inner.wip.borrow_mut(), |slot| {
if slot.is_none() { if slot.is_none() {
*slot = Some(self.inner.current_val.to_owned()); *slot = Some(self.inner.current_val.as_ref().to_owned());
} }
slot.as_mut().unwrap() slot.as_mut().unwrap()
}) })
} }
pub fn inner(self) -> T {
self.inner.current_val.as_ref().to_owned()
}
} }
impl<'a, T> std::ops::Deref for UseState<'a, T> { impl<'a, T> std::ops::Deref for UseState<'a, T> {
@@ -254,6 +269,8 @@ impl<'a, T: 'static + Display> std::fmt::Display for UseState<'a, T> {
/// A less ergonmic but still capable form of use_state that's valid for `static lifetime /// A less ergonmic but still capable form of use_state that's valid for `static lifetime
pub struct AsyncUseState<T: 'static> { pub struct AsyncUseState<T: 'static> {
inner: Rc<T>,
re_render: Rc<dyn Fn()>,
wip: Rc<RefCell<Option<T>>>, wip: Rc<RefCell<Option<T>>>,
} }
@@ -269,7 +286,17 @@ impl<T: ToOwned> AsyncUseState<T> {
slot.as_mut().unwrap() slot.as_mut().unwrap()
}) })
} }
}
impl<T> AsyncUseState<T> {
pub fn set(&mut self, val: T) { pub fn set(&mut self, val: T) {
(self.re_render)();
*self.wip.borrow_mut() = Some(val); *self.wip.borrow_mut() = Some(val);
} }
pub fn get(&self) -> &T {
self.inner.as_ref()
}
pub fn get_rc(&self) -> &Rc<T> {
&self.inner
}
} }
+227
View File
@@ -0,0 +1,227 @@
use dioxus_core::prelude::Context;
use std::{
borrow::{Borrow, BorrowMut},
cell::{Cell, Ref, RefCell, RefMut},
fmt::{Debug, Display},
ops::Not,
rc::Rc,
};
// /// Store state between component renders!
// ///
// /// ## Dioxus equivalent of UseStateInner2, designed for Rust
// ///
// /// The Dioxus version of `UseStateInner2` is the "king daddy" of state management. It allows you to ergonomically store and
// /// modify state between component renders. When the state is updated, the component will re-render.
// ///
// /// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system.
// ///
// /// [`use_state`] exposes a few helper methods to modify the underlying state:
// /// - `.set(new)` allows you to override the "work in progress" value with a new value
// /// - `.get_mut()` allows you to modify the WIP value
// /// - `.get_wip()` allows you to access the WIP value
// /// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required)
// ///
// /// Additionally, a ton of std::ops traits are implemented for the `UseStateInner2` wrapper, meaning any mutative type operations
// /// will automatically be called on the WIP value.
// ///
// /// ## Combinators
// ///
// /// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality:
// /// - `.classic()` and `.split()` convert the hook into the classic React-style hook
// /// ```rust
// /// let (state, set_state) = use_state(cx, || 10).split()
// /// ```
// ///
// ///
// /// Usage:
// /// ```ignore
// /// const Example: FC<()> = |cx, props|{
// /// let counter = use_state(cx, || 0);
// /// let increment = |_| counter += 1;
// /// let decrement = |_| counter += 1;
// ///
// /// html! {
// /// <div>
// /// <h1>"Counter: {counter}" </h1>
// /// <button onclick={increment}> "Increment" </button>
// /// <button onclick={decrement}> "Decrement" </button>
// /// </div>
// /// }
// /// }
// /// ```
// pub fn use_state2<'a, T: 'static>(
// cx: Context<'a>,
// initial_state_fn: impl FnOnce() -> T,
// ) -> &'a UseState2<T> {
// cx.use_hook(
// move |_| {
// UseState2(Rc::new(UseStateInner2 {
// current_val: initial_state_fn(),
// update_callback: cx.schedule_update(),
// wip: None,
// update_scheuled: Cell::new(false),
// }))
// },
// move |hook: &mut UseState2<T>| {
// {
// let r = hook.0.as_ref();
// let mut state = r.borrow_mut();
// state.update_scheuled.set(false);
// if state.wip.is_some() {
// state.current_val = state.wip.take().unwrap();
// }
// }
// &*hook
// },
// )
// }
// pub struct UseState2<T: 'static>(Rc<UseStateInner2<T>>);
// impl<T> ToOwned for UseState2<T> {
// type Owned = UseState2<T>;
// fn to_owned(&self) -> Self::Owned {
// UseState2(self.0.clone())
// }
// }
// pub struct UseStateInner2<T: 'static> {
// current_val: T,
// update_scheuled: Cell<bool>,
// update_callback: Rc<dyn Fn()>,
// wip: Option<T>,
// }
// impl<T: Debug> Debug for UseStateInner2<T> {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// write!(f, "{:?}", self.current_val)
// }
// }
// impl<T> UseState2<T> {
// /// Tell the Dioxus Scheduler that we need to be processed
// pub fn needs_update(&self) {
// if !self.update_scheuled.get() {
// self.update_scheuled.set(true);
// (self.update_callback)();
// }
// }
// pub fn set(&mut self, new_val: T) -> Option<T> {
// self.needs_update();
// ip.wip.replace(new_val)
// }
// pub fn get(&self) -> &T {
// &self.current_val
// }
// }
// // impl<T: 'static + ToOwned<Owned = T>> UseState2<T> {
// // /// Gain mutable access to the new value. This method is only available when the value is a `ToOwned` type.
// // ///
// // /// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value.
// // ///
// // /// To get a reference to the current value, use `.get()`
// // pub fn modify(&self) -> RefMut<T> {
// // // make sure we get processed
// // self.0.needs_update();
// // // Bring out the new value, cloning if it we need to
// // // "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
// // RefMut::map(self.wip.borrow_mut(), |slot| {
// // if slot.is_none() {
// // *slot = Some(self.current_val.to_owned());
// // }
// // slot.as_mut().unwrap()
// // })
// // }
// // pub fn inner(self) -> T {
// // self.current_val.to_owned()
// // }
// // }
// impl<T> std::ops::Deref for UseStateInner2<T> {
// type Target = T;
// fn deref(&self) -> &Self::Target {
// self.get()
// }
// }
// use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
// use crate::UseState;
// impl<T: Copy + Add<T, Output = T>> Add<T> for UseStateInner2<T> {
// type Output = T;
// fn add(self, rhs: T) -> Self::Output {
// self.current_val.add(rhs)
// }
// }
// impl<T: Copy + Add<T, Output = T>> AddAssign<T> for UseStateInner2<T> {
// fn add_assign(&mut self, rhs: T) {
// self.set(self.current_val.add(rhs));
// }
// }
// impl<T: Copy + Sub<T, Output = T>> Sub<T> for UseStateInner2<T> {
// type Output = T;
// fn sub(self, rhs: T) -> Self::Output {
// self.current_val.sub(rhs)
// }
// }
// impl<T: Copy + Sub<T, Output = T>> SubAssign<T> for UseStateInner2<T> {
// fn sub_assign(&mut self, rhs: T) {
// self.set(self.current_val.sub(rhs));
// }
// }
// /// MUL
// impl<T: Copy + Mul<T, Output = T>> Mul<T> for UseStateInner2<T> {
// type Output = T;
// fn mul(self, rhs: T) -> Self::Output {
// self.current_val.mul(rhs)
// }
// }
// impl<T: Copy + Mul<T, Output = T>> MulAssign<T> for UseStateInner2<T> {
// fn mul_assign(&mut self, rhs: T) {
// self.set(self.current_val.mul(rhs));
// }
// }
// /// DIV
// impl<T: Copy + Div<T, Output = T>> Div<T> for UseStateInner2<T> {
// type Output = T;
// fn div(self, rhs: T) -> Self::Output {
// self.current_val.div(rhs)
// }
// }
// impl<T: Copy + Div<T, Output = T>> DivAssign<T> for UseStateInner2<T> {
// fn div_assign(&mut self, rhs: T) {
// self.set(self.current_val.div(rhs));
// }
// }
// impl<V, T: PartialEq<V>> PartialEq<V> for UseStateInner2<T> {
// fn eq(&self, other: &V) -> bool {
// self.get() == other
// }
// }
// impl<O, T: Not<Output = O> + Copy> Not for UseStateInner2<T> {
// type Output = O;
// fn not(self) -> Self::Output {
// !*self.get()
// }
// }
// // enable displaty for the handle
// impl<T: 'static + Display> std::fmt::Display for UseStateInner2<T> {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// write!(f, "{}", self.current_val)
// }
// }
+5 -1
View File
@@ -44,7 +44,11 @@ pub mod on {
// ie copy // ie copy
let shortname: &'static str = &event_name[2..]; let shortname: &'static str = &event_name[2..];
c.listener(shortname, callback) let handler = EventHandler {
callback: bump.alloc(std::cell::RefCell::new(Some(callback))),
};
c.listener(shortname, handler)
} }
)* )*
)* )*
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "dioxus-liveview"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
axum = { version = "0.4.2", optional = true, features = ["ws", "headers"] }
[features]
default = ["axum"]
[dev-dependencies]
tokio = { version = "1.14.0", features = ["full"] }
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
tower-http = { version = "0.2.0", features = ["fs", "trace"] }
headers = "0.3"
+49
View File
@@ -0,0 +1,49 @@
# Dioxus LiveView
Enabling server-rendered and hybrid applications with incredibly low latency (<1ms).
```rust
#[async_std::main]
async fn main() -> tide::Result<()> {
let liveview_pool = dioxus::liveview::pool::default();
let mut app = tide::new();
// serve the liveview client
app.at("/").get(dioxus::liveview::liveview_frontend);
// and then connect the client to the backend
app.at("/app").get(|req| dioxus::liveview::launch(App, Props { req }))
app.listen("127.0.0.1:8080").await?;
Ok(())
}
```
Dioxus LiveView runs your Dioxus apps on the server
```rust
use soyuz::prelude::*;
#[tokio::main]
async fn main() {
let mut app = soyuz::new();
app.at("/app").get(websocket(handler));
app.listen("127.0.0.1:8080").await.unwrap();
}
async fn order_shoes(mut req: WebsocketRequest) -> Response {
let stream = req.upgrade();
dioxus::liveview::launch(App, stream).await;
}
fn App(cx: Context, props: &()) -> Element {
let mut count = use_state(cx, || 0);
cx.render(rsx!(
button { onclick: move |_| count += 1, "Incr" }
button { onclick: move |_| count -= 1, "Decr" }
))
}
```
+96
View File
@@ -0,0 +1,96 @@
//! Example websocket server.
//!
//! Run with
//!
//! ```not_rust
//! cargo run -p example-websockets
//! ```
use axum::{
extract::{
ws::{Message, WebSocket, WebSocketUpgrade},
TypedHeader,
},
http::StatusCode,
response::IntoResponse,
routing::{get, get_service},
Router,
};
use std::net::SocketAddr;
use tower_http::{
services::ServeDir,
trace::{DefaultMakeSpan, TraceLayer},
};
#[tokio::main]
async fn main() {
// Set the RUST_LOG, if it hasn't been explicitly defined
if std::env::var_os("RUST_LOG").is_none() {
std::env::set_var("RUST_LOG", "example_websockets=debug,tower_http=debug")
}
tracing_subscriber::fmt::init();
// build our application with some routes
let app = Router::new()
.fallback(
get_service(
ServeDir::new("examples/axum_assets").append_index_html_on_directories(true),
)
.handle_error(|error: std::io::Error| async move {
(
StatusCode::INTERNAL_SERVER_ERROR,
format!("Unhandled internal error: {}", error),
)
}),
)
// routes are matched from bottom to top, so we have to put `nest` at the
// top since it matches all routes
.route("/ws", get(ws_handler))
// logging so we can see whats going on
.layer(
TraceLayer::new_for_http()
.make_span_with(DefaultMakeSpan::default().include_headers(true)),
);
// run it with hyper
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
tracing::debug!("listening on {}", addr);
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn ws_handler(
ws: WebSocketUpgrade,
user_agent: Option<TypedHeader<headers::UserAgent>>,
) -> impl IntoResponse {
if let Some(TypedHeader(user_agent)) = user_agent {
println!("`{}` connected", user_agent.as_str());
}
ws.on_upgrade(handle_socket)
}
async fn handle_socket(mut socket: WebSocket) {
if let Some(msg) = socket.recv().await {
if let Ok(msg) = msg {
println!("Client says: {:?}", msg);
} else {
println!("client disconnected");
return;
}
}
loop {
if socket
.send(Message::Text(String::from("Hi!")))
.await
.is_err()
{
println!("client disconnected");
return;
}
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
}
@@ -0,0 +1 @@
<script src='script.js'></script>
@@ -0,0 +1,9 @@
const socket = new WebSocket('ws://localhost:3000/ws');
socket.addEventListener('open', function (event) {
socket.send('Hello Server!');
});
socket.addEventListener('message', function (event) {
console.log('Message from server ', event.data);
});
View File
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
Whitespace-only changes.
+8 -5
View File
@@ -26,11 +26,14 @@ fn init_logging() {
static HTML_CONTENT: &'static str = include_str!("../../desktop/src/index.html"); static HTML_CONTENT: &'static str = include_str!("../../desktop/src/index.html");
pub fn launch(root: FC<()>, builder: fn(WindowBuilder) -> WindowBuilder) -> anyhow::Result<()> { pub fn launch(
root: Component<()>,
builder: fn(WindowBuilder) -> WindowBuilder,
) -> anyhow::Result<()> {
launch_with_props(root, (), builder) launch_with_props(root, (), builder)
} }
pub fn launch_with_props<P: 'static + Send>( pub fn launch_with_props<P: 'static + Send>(
root: FC<P>, root: Component<P>,
props: P, props: P,
builder: fn(WindowBuilder) -> WindowBuilder, builder: fn(WindowBuilder) -> WindowBuilder,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
@@ -41,7 +44,7 @@ pub fn launch_with_props<P: 'static + Send>(
/// Components used in WebviewRenderer instances can directly use system libraries, access the filesystem, and multithread with ease. /// Components used in WebviewRenderer instances can directly use system libraries, access the filesystem, and multithread with ease.
pub struct WebviewRenderer<T> { pub struct WebviewRenderer<T> {
/// The root component used to render the Webview /// The root component used to render the Webview
root: FC<T>, root: Component<T>,
} }
enum RpcEvent<'a> { enum RpcEvent<'a> {
Initialize { Initialize {
@@ -52,7 +55,7 @@ enum RpcEvent<'a> {
impl<T: 'static + Send> WebviewRenderer<T> { impl<T: 'static + Send> WebviewRenderer<T> {
pub fn run( pub fn run(
root: FC<T>, root: Component<T>,
props: T, props: T,
user_builder: fn(WindowBuilder) -> WindowBuilder, user_builder: fn(WindowBuilder) -> WindowBuilder,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
@@ -60,7 +63,7 @@ impl<T: 'static + Send> WebviewRenderer<T> {
} }
pub fn run_with_edits( pub fn run_with_edits(
root: FC<T>, root: Component<T>,
props: T, props: T,
user_builder: fn(WindowBuilder) -> WindowBuilder, user_builder: fn(WindowBuilder) -> WindowBuilder,
redits: Option<Vec<DomEdit<'static>>>, redits: Option<Vec<DomEdit<'static>>>,
+1 -1
View File
@@ -9,7 +9,7 @@ fn main() {
dioxus_web::launch(App, |c| c); dioxus_web::launch(App, |c| c);
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
enum Route { enum Route {
Home, Home,
+5 -5
View File
@@ -301,13 +301,13 @@ mod tests {
use dioxus_core_macro::*; use dioxus_core_macro::*;
use dioxus_html as dioxus_elements; use dioxus_html as dioxus_elements;
static SIMPLE_APP: FC<()> = |cx, _| { static SIMPLE_APP: Component<()> = |cx, _| {
cx.render(rsx!(div { cx.render(rsx!(div {
"hello world!" "hello world!"
})) }))
}; };
static SLIGHTLY_MORE_COMPLEX: FC<()> = |cx, _| { static SLIGHTLY_MORE_COMPLEX: Component<()> = |cx, _| {
cx.render(rsx! { cx.render(rsx! {
div { div {
title: "About W3Schools" title: "About W3Schools"
@@ -326,14 +326,14 @@ mod tests {
}) })
}; };
static NESTED_APP: FC<()> = |cx, _| { static NESTED_APP: Component<()> = |cx, _| {
cx.render(rsx!( cx.render(rsx!(
div { div {
SIMPLE_APP {} SIMPLE_APP {}
} }
)) ))
}; };
static FRAGMENT_APP: FC<()> = |cx, _| { static FRAGMENT_APP: Component<()> = |cx, _| {
cx.render(rsx!( cx.render(rsx!(
div { "f1" } div { "f1" }
div { "f2" } div { "f2" }
@@ -389,7 +389,7 @@ mod tests {
#[test] #[test]
fn styles() { fn styles() {
static STLYE_APP: FC<()> = |cx, _| { static STLYE_APP: Component<()> = |cx, _| {
cx.render(rsx! { cx.render(rsx! {
div { color: "blue", font_size: "46px" } div { color: "blue", font_size: "46px" }
}) })
+1 -1
View File
@@ -14,7 +14,7 @@ fn main() {
dioxus_web::launch(App, |c| c); dioxus_web::launch(App, |c| c);
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
let mut count = use_state(cx, || 0); let mut count = use_state(cx, || 0);
cx.push_task(|| async move { cx.push_task(|| async move {
+1 -1
View File
@@ -107,7 +107,7 @@ impl Label {
} }
} }
static App: FC<()> = |cx, _props| { static App: Component<()> = |cx, _props| {
let mut items = use_ref(cx, || vec![]); let mut items = use_ref(cx, || vec![]);
let mut selected = use_state(cx, || None); let mut selected = use_state(cx, || None);
+1 -1
View File
@@ -15,7 +15,7 @@ fn main() {
dioxus_web::launch(App, |c| c); dioxus_web::launch(App, |c| c);
} }
static App: FC<()> = |cx, props| { static App: Component<()> = |cx, props| {
let show = use_state(cx, || true); let show = use_state(cx, || true);
let inner = match *show { let inner = match *show {
+1 -1
View File
@@ -14,7 +14,7 @@ fn main() {
dioxus_web::launch(App, |c| c); dioxus_web::launch(App, |c| c);
} }
static App: FC<()> = |cx, _| { static App: Component<()> = |cx, _| {
let doggo = cx.suspend(|| async move { let doggo = cx.suspend(|| async move {
#[derive(serde::Deserialize)] #[derive(serde::Deserialize)]
struct Doggo { struct Doggo {
+298 -311
View File
@@ -1,311 +1,298 @@
/// Wasm-bindgen has a performance option to intern commonly used phrases pub static BUILTIN_INTERNED_STRINGS: &[&'static str] = &[
/// This saves the decoding cost, making the interaction of Rust<->JS more performant. // Important tags to dioxus
/// We intern all the HTML tags and attributes, making most operations much faster. "dioxus-id",
/// "dioxus",
/// Interning takes < 1ms at the start of the app, but saves a *ton* of time later on. "dioxus-event-click", // todo: more events
/// "click",
/// Eventually we might want to procedurally generate these strings for common words, phrases, and values. // All the HTML Tags
pub(crate) fn intern_cached_strings() { "a",
let cached_words = [ "abbr",
// Important tags to dioxus "address",
"dioxus-id", "area",
"dioxus", "article",
"dioxus-event-click", // todo: more events "aside",
"click", "audio",
// All the HTML Tags "b",
"a", "base",
"abbr", "bdi",
"address", "bdo",
"area", "big",
"article", "blockquote",
"aside", "body",
"audio", "br",
"b", "button",
"base", "canvas",
"bdi", "caption",
"bdo", "cite",
"big", "code",
"blockquote", "col",
"body", "colgroup",
"br", "command",
"button", "data",
"canvas", "datalist",
"caption", "dd",
"cite", "del",
"code", "details",
"col", "dfn",
"colgroup", "dialog",
"command", "div",
"data", "dl",
"datalist", "dt",
"dd", "em",
"del", "embed",
"details", "fieldset",
"dfn", "figcaption",
"dialog", "figure",
"div", "footer",
"dl", "form",
"dt", "h1",
"em", "h2",
"embed", "h3",
"fieldset", "h4",
"figcaption", "h5",
"figure", "h6",
"footer", "head",
"form", "header",
"h1", "hr",
"h2", "html",
"h3", "i",
"h4", "iframe",
"h5", "img",
"h6", "input",
"head", "ins",
"header", "kbd",
"hr", "keygen",
"html", "label",
"i", "legend",
"iframe", "li",
"img", "link",
"input", "main",
"ins", "map",
"kbd", "mark",
"keygen", "menu",
"label", "menuitem",
"legend", "meta",
"li", "meter",
"link", "nav",
"main", "noscript",
"map", "object",
"mark", "ol",
"menu", "optgroup",
"menuitem", "option",
"meta", "output",
"meter", "p",
"nav", "param",
"noscript", "picture",
"object", "pre",
"ol", "progress",
"optgroup", "q",
"option", "rp",
"output", "rt",
"p", "ruby",
"param", "s",
"picture", "samp",
"pre", "script",
"progress", "section",
"q", "select",
"rp", "small",
"rt", "source",
"ruby", "span",
"s", "strong",
"samp", "style",
"script", "sub",
"section", "summary",
"select", "sup",
"small", "table",
"source", "tbody",
"span", "td",
"strong", "textarea",
"style", "tfoot",
"sub", "th",
"summary", "thead",
"sup", "time",
"table", "title",
"tbody", "tr",
"td", "track",
"textarea", "u",
"tfoot", "ul",
"th", "var",
"thead", "video",
"time", "wbr",
"title", // All the event handlers
"tr", "Attribute",
"track", "accept",
"u", "accept-charset",
"ul", "accesskey",
"var", "action",
"video", "alt",
"wbr", "async",
// All the event handlers "autocomplete",
"Attribute", "autofocus",
"accept", "autoplay",
"accept-charset", "charset",
"accesskey", "checked",
"action", "cite",
"alt", "class",
"async", "cols",
"autocomplete", "colspan",
"autofocus", "content",
"autoplay", "contenteditable",
"charset", "controls",
"checked", "coords",
"cite", "data",
"class", "data-*",
"cols", "datetime",
"colspan", "default",
"content", "defer",
"contenteditable", "dir",
"controls", "dirname",
"coords", "disabled",
"data", "download",
"data-*", "draggable",
"datetime", "enctype",
"default", "for",
"defer", "form",
"dir", "formaction",
"dirname", "headers",
"disabled", "height",
"download", "hidden",
"draggable", "high",
"enctype", "href",
"for", "hreflang",
"form", "http-equiv",
"formaction", "id",
"headers", "ismap",
"height", "kind",
"hidden", "label",
"high", "lang",
"href", "list",
"hreflang", "loop",
"http-equiv", "low",
"id", "max",
"ismap", "maxlength",
"kind", "media",
"label", "method",
"lang", "min",
"list", "multiple",
"loop", "muted",
"low", "name",
"max", "novalidate",
"maxlength", "onabort",
"media", "onafterprint",
"method", "onbeforeprint",
"min", "onbeforeunload",
"multiple", "onblur",
"muted", "oncanplay",
"name", "oncanplaythrough",
"novalidate", "onchange",
"onabort", "onclick",
"onafterprint", "oncontextmenu",
"onbeforeprint", "oncopy",
"onbeforeunload", "oncuechange",
"onblur", "oncut",
"oncanplay", "ondblclick",
"oncanplaythrough", "ondrag",
"onchange", "ondragend",
"onclick", "ondragenter",
"oncontextmenu", "ondragleave",
"oncopy", "ondragover",
"oncuechange", "ondragstart",
"oncut", "ondrop",
"ondblclick", "ondurationchange",
"ondrag", "onemptied",
"ondragend", "onended",
"ondragenter", "onerror",
"ondragleave", "onfocus",
"ondragover", "onhashchange",
"ondragstart", "oninput",
"ondrop", "oninvalid",
"ondurationchange", "onkeydown",
"onemptied", "onkeypress",
"onended", "onkeyup",
"onerror", "onload",
"onfocus", "onloadeddata",
"onhashchange", "onloadedmetadata",
"oninput", "onloadstart",
"oninvalid", "onmousedown",
"onkeydown", "onmousemove",
"onkeypress", "onmouseout",
"onkeyup", "onmouseover",
"onload", "onmouseup",
"onloadeddata", "onmousewheel",
"onloadedmetadata", "onoffline",
"onloadstart", "ononline",
"onmousedown", "onpageshow",
"onmousemove", "onpaste",
"onmouseout", "onpause",
"onmouseover", "onplay",
"onmouseup", "onplaying",
"onmousewheel", "onprogress",
"onoffline", "onratechange",
"ononline", "onreset",
"onpageshow", "onresize",
"onpaste", "onscroll",
"onpause", "onsearch",
"onplay", "onseeked",
"onplaying", "onseeking",
"onprogress", "onselect",
"onratechange", "onstalled",
"onreset", "onsubmit",
"onresize", "onsuspend",
"onscroll", "ontimeupdate",
"onsearch", "ontoggle",
"onseeked", "onunload",
"onseeking", "onvolumechange",
"onselect", "onwaiting",
"onstalled", "onwheel",
"onsubmit", "open",
"onsuspend", "optimum",
"ontimeupdate", "pattern",
"ontoggle", "placeholder",
"onunload", "poster",
"onvolumechange", "preload",
"onwaiting", "readonly",
"onwheel", "rel",
"open", "required",
"optimum", "reversed",
"pattern", "rows",
"placeholder", "rowspan",
"poster", "sandbox",
"preload", "scope",
"readonly", "selected",
"rel", "shape",
"required", "size",
"reversed", "sizes",
"rows", "span",
"rowspan", "spellcheck",
"sandbox", "src",
"scope", "srcdoc",
"selected", "srclang",
"shape", "srcset",
"size", "start",
"sizes", "step",
"span", "style",
"spellcheck", "tabindex",
"src", "target",
"srcdoc", "title",
"srclang", "translate",
"srcset", "type",
"start", "usemap",
"step", "value",
"style", "width",
"tabindex", "wrap",
"target", "0",
"title", "1",
"translate", "2",
"type", "3",
"usemap", "4",
"value", "5",
"width", "6",
"wrap", "7",
"0", "8",
"1", "9",
"2", "10",
"3", "11",
"4", "12",
"5", "13",
"6", "14",
"7", ];
"8",
"9",
"10",
"11",
"12",
"13",
"14",
];
for s in cached_words {
wasm_bindgen::intern(s);
}
}
+10
View File
@@ -11,6 +11,7 @@
pub struct WebConfig { pub struct WebConfig {
pub(crate) hydrate: bool, pub(crate) hydrate: bool,
pub(crate) rootname: String, pub(crate) rootname: String,
pub(crate) cached_strings: Vec<String>,
} }
impl Default for WebConfig { impl Default for WebConfig {
@@ -18,6 +19,7 @@ impl Default for WebConfig {
Self { Self {
hydrate: false, hydrate: false,
rootname: "main".to_string(), rootname: "main".to_string(),
cached_strings: Vec::new(),
} }
} }
} }
@@ -41,4 +43,12 @@ impl WebConfig {
self.rootname = name.into(); self.rootname = name.into();
self self
} }
/// Set the name of the element that Dioxus will use as the root.
///
/// This is akint to calling React.render() on the element with the specified name.
pub fn with_string_cache(mut self, cache: Vec<String>) -> Self {
self.cached_strings = cache;
self
}
} }
+21 -18
View File
@@ -56,11 +56,10 @@ use std::rc::Rc;
pub use crate::cfg::WebConfig; pub use crate::cfg::WebConfig;
use crate::dom::load_document; use crate::dom::load_document;
use cache::intern_cached_strings;
use dioxus::SchedulerMsg; use dioxus::SchedulerMsg;
use dioxus::VirtualDom; use dioxus::VirtualDom;
pub use dioxus_core as dioxus; pub use dioxus_core as dioxus;
use dioxus_core::prelude::FC; use dioxus_core::prelude::Component;
use futures_util::FutureExt; use futures_util::FutureExt;
mod cache; mod cache;
@@ -82,15 +81,15 @@ mod ric_raf;
/// ///
/// ```rust /// ```rust
/// fn main() { /// fn main() {
/// dioxus_web::launch(App, |c| c); /// dioxus_web::launch(App);
/// } /// }
/// ///
/// static App: FC<()> = |cx, props| { /// static App: Component<()> = |cx, props| {
/// rsx!(cx, div {"hello world"}) /// rsx!(cx, div {"hello world"})
/// } /// }
/// ``` /// ```
pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> WebConfig) { pub fn launch(root_component: Component<()>) {
launch_with_props(root_component, (), configuration) launch_with_props(root_component, (), |c| c);
} }
/// Launches the VirtualDOM from the specified component function and props. /// Launches the VirtualDOM from the specified component function and props.
@@ -109,12 +108,15 @@ pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> W
/// name: String /// name: String
/// } /// }
/// ///
/// static App: FC<RootProps> = |cx, props| { /// static App: Component<RootProps> = |cx, props| {
/// rsx!(cx, div {"hello {props.name}"}) /// rsx!(cx, div {"hello {props.name}"})
/// } /// }
/// ``` /// ```
pub fn launch_with_props<T, F>(root_component: FC<T>, root_properties: T, configuration_builder: F) pub fn launch_with_props<T, F>(
where root_component: Component<T>,
root_properties: T,
configuration_builder: F,
) where
T: Send + 'static, T: Send + 'static,
F: FnOnce(WebConfig) -> WebConfig, F: FnOnce(WebConfig) -> WebConfig,
{ {
@@ -134,10 +136,15 @@ where
/// wasm_bindgen_futures::spawn_local(app_fut); /// wasm_bindgen_futures::spawn_local(app_fut);
/// } /// }
/// ``` /// ```
pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg: WebConfig) { pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T, cfg: WebConfig) {
let mut dom = VirtualDom::new_with_props(root, root_props); let mut dom = VirtualDom::new_with_props(root, root_props);
intern_cached_strings(); for s in crate::cache::BUILTIN_INTERNED_STRINGS {
wasm_bindgen::intern(s);
}
for s in &cfg.cached_strings {
wasm_bindgen::intern(s);
}
let should_hydrate = cfg.hydrate; let should_hydrate = cfg.hydrate;
@@ -155,7 +162,6 @@ pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg:
// hydrating is simply running the dom for a single render. If the page is already written, then the corresponding // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
// ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
if !should_hydrate { if !should_hydrate {
// log::info!("Applying rebuild edits..., {:?}", mutations);
websys_dom.process_edits(&mut mutations.edits); websys_dom.process_edits(&mut mutations.edits);
} }
@@ -166,20 +172,17 @@ pub async fn run_with_props<T: 'static + Send>(root: FC<T>, root_props: T, cfg:
// if there is work then this future resolves immediately. // if there is work then this future resolves immediately.
dom.wait_for_work().await; dom.wait_for_work().await;
// // wait for the mainthread to schedule us in // wait for the mainthread to schedule us in
// let mut deadline = work_loop.wait_for_idle_time().await; let mut deadline = work_loop.wait_for_idle_time().await;
// run the virtualdom work phase until the frame deadline is reached // run the virtualdom work phase until the frame deadline is reached
let mutations = dom.work_with_deadline(|| false); let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
// // run the virtualdom work phase until the frame deadline is reached
// let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
// wait for the animation frame to fire so we can apply our changes // wait for the animation frame to fire so we can apply our changes
work_loop.wait_for_raf().await; work_loop.wait_for_raf().await;
for mut edit in mutations { for mut edit in mutations {
// actually apply our changes during the animation frame // actually apply our changes during the animation frame
// log::info!("Applying change edits..., {:?}", edit);
websys_dom.process_edits(&mut edit.edits); websys_dom.process_edits(&mut edit.edits);
} }
} }