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-ssr = { path = "./packages/ssr", optional = true }
dioxus-mobile = { path = "./packages/mobile", optional = true }
dioxus-liveview = { path = "./packages/liveview", optional = true }
[features]
# core
@@ -26,6 +27,7 @@ macro = ["dioxus-core-macro"]
hooks = ["dioxus-hooks"]
html = ["dioxus-html"]
router = ["dioxus-router"]
liveview = ["dioxus-liveview"]
# utilities
atoms = []
+1 -1
View File
@@ -11,7 +11,7 @@ async fn main() {
dioxus::desktop::launch(App, |c| c);
}
pub static App: FC<()> = |cx, _| {
pub static App: Component<()> = |cx, _| {
let count = use_state(cx, || 0);
let mut direction = use_state(cx, || 1);
+1 -1
View File
@@ -13,7 +13,7 @@ fn main() {
dioxus::desktop::launch(APP, |cfg| cfg);
}
const APP: FC<()> = |cx, _| {
const APP: Component<()> = |cx, _| {
let cur_val = use_state(cx, || 0.0_f64);
let operator = use_state(cx, || None as Option<&'static str>);
let display_value = use_state(cx, || String::from(""));
+1 -1
View File
@@ -27,7 +27,7 @@ fn main() {
use dioxus::prelude::*;
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
let p1 = use_state(cx, || 0);
let p2 = use_state(cx, || 0);
+1 -1
View File
@@ -19,7 +19,7 @@ pub struct Client {
pub description: String,
}
static App: FC<()> = |cx, _| {
static App: Component<()> = |cx, _| {
let mut clients = use_ref(cx, || vec![] as Vec<Client>);
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 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 selected = use_state(cx, || None);
+1 -1
View File
@@ -19,7 +19,7 @@ fn main() {
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);
cx.render(rsx! {
+1 -1
View File
@@ -36,7 +36,7 @@ fn main() {
dioxus_desktop::run(APP, (), |c| c.with_edits(edits));
}
const APP: FC<()> = |(cx, _props)| {
const APP: Component<()> = |(cx, _props)| {
rsx!(cx, div {
"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 clear_display = state.read().display_value.eq("0");
+1 -1
View File
@@ -11,7 +11,7 @@ fn main() {
dioxus::desktop::launch(App, |c| c);
}
pub static App: FC<()> = |cx, _| {
pub static App: Component<()> = |cx, _| {
let state = use_state(cx, PlayerState::new);
let is_playing = state.is_playing();
+1 -1
View File
@@ -7,7 +7,7 @@ fn main() {
dioxus::desktop::launch(App, |c| c);
}
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
let mut count = use_state(cx, || 0);
cx.render(rsx! {
+1 -1
View File
@@ -23,7 +23,7 @@ pub enum Route {
NotFound,
}
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
let route = use_router(cx, Route::parse)?;
cx.render(rsx! {
+1 -1
View File
@@ -49,7 +49,7 @@ const NONE_ELEMENT: Option<()> = None;
use baller::Baller;
use dioxus::prelude::*;
pub static Example: FC<()> = |cx, props| {
pub static Example: Component<()> = |cx, props| {
let formatting = "formatting!";
let formatting_tuple = ("a", "b");
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));
}
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
cx.render(rsx!(
div {
h1 { "Title" }
+6 -6
View File
@@ -14,7 +14,7 @@ fn main() {
const STYLE: &str = "body {overflow:hidden;}";
pub static App: FC<()> = |cx, props| {
pub static App: Component<()> = |cx, props| {
cx.render(rsx!(
div { class: "overflow-hidden"
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! {
div {
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! {
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! {
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!(
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!(
svg {
fill: "none"
+1 -1
View File
@@ -9,7 +9,7 @@ fn main() {
dioxus::desktop::launch(App, |c| c);
}
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
let mut count = use_state(cx, || 0);
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 App: FC<()> = |cx, props| {
const App: Component<()> = |cx, props| {
let mut draft = use_state(cx, || "".to_string());
let mut todos = use_state(cx, || HashMap::<u32, Rc<TodoItem>>::new());
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";
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
//
let body = use_suspense(
cx,
@@ -40,7 +40,7 @@ static App: FC<()> = |cx, props| {
#[derive(PartialEq, Props)]
struct WeatherProps {}
static WeatherDisplay: FC<WeatherProps> = |cx, props| {
static WeatherDisplay: Component<WeatherProps> = |cx, props| {
//
cx.render(rsx!(
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);
}
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
let mut rng = SmallRng::from_entropy();
let rows = (0..1_000).map(|f| {
let label = Label::new(&mut rng);
+1 -1
View File
@@ -16,7 +16,7 @@ fn main() {
dioxus::web::launch(App, |c| c);
}
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
let mut count = use_state(cx, || 0);
cx.render(rsx! {
+1 -1
View File
@@ -23,7 +23,7 @@ criterion_group!(mbenches, create_rows);
criterion_main!(mbenches);
fn create_rows(c: &mut Criterion) {
static App: FC<()> = |cx, _| {
static App: Component<()> = |cx, _| {
let mut rng = SmallRng::from_entropy();
let rows = (0..10_000_usize).map(|f| {
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::*;
fn main() {
static App: FC<()> = |cx, _| {
static App: Component<()> = |cx, _| {
let mut rng = SmallRng::from_entropy();
let rows = (0..10_000_usize).map(|f| {
let label = Label::new(&mut rng);
+4 -2
View File
@@ -1,3 +1,5 @@
#![allow(non_snake_case)]
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
@@ -7,8 +9,8 @@ fn main() {
let _ = VirtualDom::new(Parent);
}
fn Parent(cx: Context, props: &()) -> Element {
let value = cx.use_hook(|_| String::new(), |f| &*f);
fn Parent(cx: Context, _props: &()) -> Element {
let value = cx.use_hook(|_| String::new(), |f| f);
cx.render(rsx! {
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
/// stack machines all the way down!
pub struct DiffState<'bump> {
scopes: &'bump ScopeArena,
pub mutations: Mutations<'bump>,
pub(crate) struct DiffState<'bump> {
pub(crate) scopes: &'bump ScopeArena,
pub(crate) mutations: Mutations<'bump>,
pub(crate) stack: DiffStack<'bump>,
pub force_diff: bool,
pub(crate) force_diff: bool,
}
impl<'bump> DiffState<'bump> {
@@ -149,7 +149,7 @@ pub(crate) enum DiffInstruction<'a> {
}
#[derive(Debug, Clone, Copy)]
pub enum MountType<'a> {
pub(crate) enum MountType<'a> {
Absorb,
Append,
Replace { old: &'a VNode<'a> },
@@ -159,9 +159,9 @@ pub enum MountType<'a> {
pub(crate) struct DiffStack<'bump> {
pub(crate) instructions: Vec<DiffInstruction<'bump>>,
nodes_created_stack: SmallVec<[usize; 10]>,
pub scope_stack: SmallVec<[ScopeId; 5]>,
pub element_stack: SmallVec<[ElementId; 10]>,
pub(crate) nodes_created_stack: SmallVec<[usize; 10]>,
pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
pub(crate) element_stack: SmallVec<[ElementId; 10]>,
}
impl<'bump> DiffStack<'bump> {
+8 -17
View File
@@ -1,17 +1,6 @@
#![allow(non_snake_case)]
#![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 diff;
pub(crate) mod lazynodes;
@@ -23,7 +12,7 @@ pub(crate) mod virtual_dom;
pub(crate) mod innerlude {
pub use crate::component::*;
pub use crate::diff::*;
pub(crate) use crate::diff::*;
pub use crate::lazynodes::*;
pub use crate::mutations::*;
pub use crate::nodes::*;
@@ -32,19 +21,21 @@ pub(crate) mod innerlude {
pub use crate::virtual_dom::*;
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::{
Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, IntoVNode,
LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeId,
UserEvent, VElement, VFragment, VNode, VirtualDom, FC,
Attribute, Component, Context, DioxusElement, DomEdit, Element, ElementId, EventHandler,
EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties,
SchedulerMsg, ScopeId, UserEvent, VElement, VFragment, VNode, VirtualDom,
};
pub mod prelude {
pub use crate::component::{fc_to_builder, Fragment, Properties};
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::VirtualDom;
}
-2
View File
@@ -16,7 +16,6 @@ pub struct Mutations<'a> {
pub edits: Vec<DomEdit<'a>>,
pub dirty_scopes: FxHashSet<ScopeId>,
pub refs: Vec<NodeRefMutation<'a>>,
pub effects: Vec<&'a dyn FnMut()>,
}
impl Debug for Mutations<'_> {
@@ -113,7 +112,6 @@ impl<'a> Mutations<'a> {
edits: Vec::new(),
refs: Vec::new(),
dirty_scopes: Default::default(),
effects: Vec::new(),
}
}
+28 -9
View File
@@ -338,9 +338,32 @@ pub struct Listener<'bump> {
pub event: &'static str,
/// The actual callback that the user specified
#[allow(clippy::type_complexity)]
pub(crate) callback:
RefCell<Option<BumpBox<'bump, dyn FnMut(std::sync::Arc<dyn Any + Send + Sync>) + 'bump>>>,
pub(crate) callback: EventHandler<'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
@@ -608,15 +631,11 @@ impl<'a> NodeFactory<'a> {
}))
}
pub fn listener(
self,
event: &'static str,
callback: BumpBox<'a, dyn FnMut(Arc<dyn Any + Send + Sync>) + 'a>,
) -> Listener<'a> {
pub fn listener(self, event: &'static str, callback: EventHandler<'a>) -> Listener<'a> {
Listener {
event,
mounted_node: Cell::new(None),
callback: RefCell::new(Some(callback)),
callback,
}
}
+50 -50
View File
@@ -7,6 +7,7 @@ use std::{
cell::{Cell, RefCell},
collections::HashMap,
future::Future,
pin::Pin,
rc::Rc,
};
@@ -32,6 +33,14 @@ use bumpalo::{boxed::Box as BumpBox, Bump};
/// ```
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`.
///
/// 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(crate) parent_scope: Option<*mut Scope>,
// parent element I think?
pub(crate) container: ElementId,
pub(crate) our_arena_idx: ScopeId,
@@ -75,17 +83,9 @@ pub struct Scope {
pub struct SelfReferentialItems<'a> {
pub(crate) listeners: Vec<&'a Listener<'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
impl Scope {
/// Get the subtree ID that this scope belongs to.
@@ -227,11 +227,11 @@ impl Scope {
let _ = self.sender.unbounded_send(SchedulerMsg::Immediate(id));
}
/// Get the [`ScopeId`] of a mounted component.
///
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
pub fn bump(&self) -> &Bump {
&self.wip_frame().bump
/// Get the Root Node of this scope
pub fn root_node(&self) -> &VNode {
todo!("Portals have changed how we address nodes. Still fixing this, sorry.");
// let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
// unsafe { std::mem::transmute(&*node) }
}
/// 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;
while let Some(parent_ptr) = search_parent {
// safety: all parent pointers are valid thanks to the bump arena
let parent = unsafe { &*parent_ptr };
if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
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.
///
///
///
///
/// The future is forcibly dropped if the component is not ready by the next render
pub fn push_task<'src, F: Future<Output = ()>>(
&'src self,
fut: impl FnOnce() -> F + 'src,
) -> usize
pub fn push_task<'src, F>(&'src self, fut: impl FnOnce() -> F + 'src) -> usize
where
F: Future<Output = ()>,
F::Output: 'src,
F: 'src,
{
@@ -302,18 +298,20 @@ impl Scope {
.unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
.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
//
// 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 pinned_fut: Pin<BumpBox<_>> = boxed_fut.into();
// 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();
items.tasks.push(self_ref_fut);
items.tasks.len() - 1
}
@@ -334,22 +332,19 @@ impl Scope {
/// }
///```
pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<VPortal> {
let frame = self.wip_frame();
let bump = &frame.bump;
let factory = NodeFactory { bump };
let node = rsx.map(|f| f.call(factory))?;
let node = bump.alloc(node);
let bump = &self.wip_frame().bump;
let node_ptr = node as *mut _;
let node_ptr = unsafe { std::mem::transmute(node_ptr) };
let owned_node: VNode<'src> = rsx.map(|f| f.call(NodeFactory { bump }))?;
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)),
link_idx: Cell::new(0),
node: node_ptr,
};
Some(link)
node,
})
}
/// Store a value between renders
@@ -379,12 +374,12 @@ impl Scope {
runner: impl FnOnce(&'src mut State) -> Output,
) -> Output {
let mut vals = self.hook_vals.borrow_mut();
let hook_len = vals.len();
let cur_idx = self.hook_idx.get();
if cur_idx >= hook_len {
let val = self.hook_arena.alloc(initializer(hook_len));
vals.push(val);
vals.push(self.hook_arena.alloc(initializer(hook_len)));
}
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 {
match self.generation.get() & 1 == 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 {
match self.generation.get() & 1 == 1 {
true => &self.frames[0],
@@ -432,20 +429,23 @@ impl Scope {
/// Reset this component's frame
///
/// # Safety:
///
/// 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) {
// todo: unsafecell or something
let bump = self.wip_frame_mut();
bump.bump.reset();
self.wip_frame_mut().bump.reset();
}
/// Cycle to the next generation
pub(crate) fn cycle_frame(&self) {
self.generation.set(self.generation.get() + 1);
}
pub fn root_node(&self) -> &VNode {
let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
unsafe { std::mem::transmute(&*node) }
/// Get the [`Bump`] of the WIP frame.
pub(crate) fn bump(&self) -> &Bump {
&self.wip_frame().bump
}
}
@@ -454,7 +454,7 @@ pub(crate) struct BumpFrame {
pub nodes: RefCell<Vec<*const VNode<'static>>>,
}
impl BumpFrame {
pub fn new(capacity: usize) -> Self {
pub(crate) fn new(capacity: usize) -> Self {
let bump = Bump::with_capacity(capacity);
let node = &*bump.alloc(VText {
@@ -467,7 +467,7 @@ impl BumpFrame {
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 len = nodes.len();
+23 -32
View File
@@ -9,9 +9,9 @@ use std::{
use crate::innerlude::*;
pub type FcSlot = *const ();
pub(crate) type FcSlot = *const ();
pub struct Heuristic {
pub(crate) struct Heuristic {
hook_arena_size: usize,
node_arena_size: usize,
}
@@ -92,15 +92,8 @@ impl ScopeArena {
let new_scope_id = ScopeId(self.scope_counter.get());
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() {
let scope = unsafe { &mut *old_scope };
// log::debug!(
// "reusing scope {:?} as {:?}",
// scope.our_arena_idx,
// new_scope_id
// );
scope.caller = caller;
scope.parent_scope = parent_scope;
@@ -203,8 +196,6 @@ impl ScopeArena {
pub fn try_remove(&self, id: &ScopeId) -> Option<()> {
self.ensure_drop_safety(id);
// log::debug!("removing scope {:?}", id);
// Safety:
// - ensure_drop_safety ensures that no references to this scope are in use
// - 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
/// be dropped.
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
// 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);
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();
drop_props();
});
// Now that all the references are gone, we can safely drop our own references in our listeners.
items
.listeners
.drain(..)
.for_each(|listener| drop(listener.callback.borrow_mut().take()));
// Now that all the references are gone, we can safely drop our own references in our listeners.
items
.listeners
.drain(..)
.for_each(|listener| drop(listener.callback.callback.borrow_mut().take()));
}
}
pub(crate) fn run_scope(&self, id: &ScopeId) -> bool {
@@ -375,7 +366,7 @@ impl ScopeArena {
//
for listener in real_el.listeners.borrow().iter() {
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() {
(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.
pub fn new(root: FC<()>) -> Self {
pub fn new(root: Component<()>) -> Self {
Self::new_with_props(root, ())
}
@@ -174,7 +174,7 @@ impl VirtualDom {
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
/// 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>();
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
/// 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>(
root: FC<P>,
root: Component<P>,
root_props: P,
sender: UnboundedSender<SchedulerMsg>,
receiver: UnboundedReceiver<SchedulerMsg>,
@@ -235,27 +240,55 @@ impl VirtualDom {
/// # Example
///
/// ```rust, ignore
///
///
/// let dom = VirtualDom::new(App);
/// let sender = dom.get_scheduler_channel();
/// ```
pub fn get_scheduler_channel(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
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.
///
/// # Example
///
/// ```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())
}
/// 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.
///
/// # Example
///
/// ```rust, ignore
/// let dom = VirtualDom::new(App);
/// let sender = dom.get_scheduler_channel();
/// ```
pub async fn wait_for_work(&mut self) {
loop {
if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() {
@@ -390,9 +423,10 @@ impl VirtualDom {
committed_mutations.push(mutations);
} else {
log::debug!("Could not finish work in time");
// 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;
}
}
@@ -400,7 +434,7 @@ impl VirtualDom {
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.
///
@@ -475,6 +509,15 @@ impl VirtualDom {
/// 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.
///
/// ```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> {
let scope = self.scopes.get_scope(&self.base_scope).unwrap();
let frame = scope.wip_frame();
@@ -485,7 +528,16 @@ impl VirtualDom {
/// 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> {
let mut machine = DiffState::new(&self.scopes);
machine.stack.push(DiffInstruction::Diff { new, old });
@@ -498,6 +550,16 @@ impl VirtualDom {
/// 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.
///
///
/// ```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> {
let nodes = self.render_vnodes(left);
let mut machine = DiffState::new(&self.scopes);
@@ -507,9 +569,19 @@ impl VirtualDom {
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>(
&'a self,
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
while let Some(mut task) = items.tasks.pop() {
// todo: does this make sense?
// 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() {
if task.as_mut().poll(cx).is_ready() {
all_pending = false
} else {
unfinished_tasks.push(task);
+1 -1
View File
@@ -10,7 +10,7 @@ fn test_borrowed_state() {
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);
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.
//!
@@ -13,7 +13,7 @@ use dioxus_html as dioxus_elements;
mod test_logging;
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;
test_logging::set_up_logging(IS_LOGGING_ENABLED);
VirtualDom::new_with_props(app, props)
@@ -21,7 +21,7 @@ fn new_dom<P: 'static + Send>(app: FC<P>, props: P) -> VirtualDom {
#[test]
fn test_original_diff() {
static APP: FC<()> = |cx, props| {
static APP: Component<()> = |cx, props| {
cx.render(rsx! {
div {
div {
@@ -57,7 +57,7 @@ fn test_original_diff() {
#[test]
fn create() {
static APP: FC<()> = |cx, props| {
static APP: Component<()> = |cx, props| {
cx.render(rsx! {
div {
div {
@@ -120,7 +120,7 @@ fn create() {
#[test]
fn create_list() {
static APP: FC<()> = |cx, props| {
static APP: Component<()> = |cx, props| {
cx.render(rsx! {
{(0..3).map(|f| rsx!{ div {
"hello"
@@ -169,7 +169,7 @@ fn create_list() {
#[test]
fn create_simple() {
static APP: FC<()> = |cx, props| {
static APP: Component<()> = |cx, props| {
cx.render(rsx! {
div {}
div {}
@@ -207,7 +207,7 @@ fn create_simple() {
}
#[test]
fn create_components() {
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
cx.render(rsx! {
Child { "abc1" }
Child { "abc2" }
@@ -273,7 +273,7 @@ fn create_components() {
}
#[test]
fn anchors() {
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
cx.render(rsx! {
{true.then(|| rsx!{ div { "hello" } })}
{false.then(|| rsx!{ div { "goodbye" } })}
+10 -10
View File
@@ -22,7 +22,7 @@ fn manual_diffing() {
value: Shared<&'static str>,
}
static App: FC<AppProps> = |cx, props| {
static App: Component<AppProps> = |cx, props| {
let val = props.value.lock().unwrap();
cx.render(rsx! { div { "{val}" } })
};
@@ -46,7 +46,7 @@ fn manual_diffing() {
#[test]
fn events_generate() {
static App: FC<()> = |cx, _| {
static App: Component<()> = |cx, _| {
let mut count = use_state(cx, || 0);
let inner = match *count {
@@ -69,7 +69,7 @@ fn events_generate() {
let mut dom = VirtualDom::new(App);
let mut channel = dom.get_scheduler_channel();
assert!(dom.has_any_work());
assert!(dom.has_work());
let edits = dom.rebuild();
assert_eq!(
@@ -105,7 +105,7 @@ fn events_generate() {
#[test]
fn components_generate() {
static App: FC<()> = |cx, _| {
static App: Component<()> = |cx, _| {
let mut render_phase = use_state(cx, || 0);
render_phase += 1;
@@ -122,7 +122,7 @@ fn components_generate() {
})
};
static Child: FC<()> = |cx, _| {
static Child: Component<()> = |cx, _| {
cx.render(rsx! {
h1 {}
})
@@ -222,7 +222,7 @@ fn components_generate() {
#[test]
fn component_swap() {
// simple_logger::init();
static App: FC<()> = |cx, _| {
static App: Component<()> = |cx, _| {
let mut render_phase = use_state(cx, || 0);
render_phase += 1;
@@ -261,7 +261,7 @@ fn component_swap() {
})
};
static NavBar: FC<()> = |cx, _| {
static NavBar: Component<()> = |cx, _| {
println!("running navbar");
cx.render(rsx! {
h1 {
@@ -271,7 +271,7 @@ fn component_swap() {
})
};
static NavLink: FC<()> = |cx, _| {
static NavLink: Component<()> = |cx, _| {
println!("running navlink");
cx.render(rsx! {
h1 {
@@ -280,7 +280,7 @@ fn component_swap() {
})
};
static Dashboard: FC<()> = |cx, _| {
static Dashboard: Component<()> = |cx, _| {
println!("running dashboard");
cx.render(rsx! {
div {
@@ -289,7 +289,7 @@ fn component_swap() {
})
};
static Results: FC<()> = |cx, _| {
static Results: Component<()> = |cx, _| {
println!("running results");
cx.render(rsx! {
div {
+2 -2
View File
@@ -13,12 +13,12 @@ mod test_logging;
fn shared_state_test() {
struct MySharedState(&'static str);
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
cx.provide_state(MySharedState("world!"));
cx.render(rsx!(Child {}))
};
static Child: FC<()> = |cx, props| {
static Child: Component<()> = |cx, props| {
let shared = cx.consume_state::<MySharedState>()?;
cx.render(rsx!("Hello, {shared.0}"))
};
+6 -6
View File
@@ -19,7 +19,7 @@ use DomEdit::*;
#[test]
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 edits = vdom.rebuild();
@@ -27,7 +27,7 @@ fn app_runs() {
#[test]
fn fragments_work() {
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
cx.render(rsx!(
div{"hello"}
div{"goodbye"}
@@ -41,7 +41,7 @@ fn fragments_work() {
#[test]
fn lists_work() {
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
cx.render(rsx!(
h1 {"hello"}
{(0..6).map(|f| rsx!(span{ "{f}" }))}
@@ -54,7 +54,7 @@ fn lists_work() {
#[test]
fn conditional_rendering() {
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
cx.render(rsx!(
h1 {"hello"}
{true.then(|| rsx!(span{ "a" }))}
@@ -87,13 +87,13 @@ fn conditional_rendering() {
#[test]
fn child_components() {
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
cx.render(rsx!(
{true.then(|| rsx!(Child { }))}
{false.then(|| rsx!(Child { }))}
))
};
static Child: FC<()> = |cx, props| {
static Child: Component<()> = |cx, props| {
cx.render(rsx!(
h1 {"hello"}
h1 {"goodbye"}
+1 -1
View File
@@ -15,7 +15,7 @@ fn main() {
dioxus_desktop::launch(App, |c| c);
}
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
let mut count = use_state(cx, || 0);
log::debug!("count is {:?}", count);
+4 -6
View File
@@ -15,7 +15,6 @@ use std::{
sync::atomic::AtomicBool,
sync::{Arc, RwLock},
};
use tokio::task::LocalSet;
use wry::{
application::{
accelerator::{Accelerator, SysMods},
@@ -30,14 +29,14 @@ use wry::{
};
pub fn launch(
root: FC<()>,
root: Component<()>,
config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) {
launch_with_props(root, (), config_builder)
}
pub fn launch_with_props<P: Properties + 'static + Send + Sync>(
root: FC<P>,
root: Component<P>,
props: P,
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>(
root: FC<T>,
root: Component<T>,
props: T,
user_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
) {
@@ -112,7 +111,7 @@ pub struct DesktopController {
impl DesktopController {
// Launch the virtualdom on its own thread managed by tokio
// returns the desktop state
pub fn new_on_tokio<P: Send + 'static>(root: FC<P>, props: P) -> Self {
pub fn new_on_tokio<P: Send + 'static>(root: Component<P>, props: P) -> Self {
let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
let pending_edits = edit_queue.clone();
@@ -142,7 +141,6 @@ impl DesktopController {
dom.wait_for_work().await;
let mut muts = dom.work_with_deadline(|| false);
while let Some(edit) = muts.pop() {
log::debug!("found mutations {:?}", muts);
edit_queue
.write()
.unwrap()
+6
View File
@@ -1,6 +1,9 @@
mod usestate;
pub use usestate::{use_state, AsyncUseState, UseState};
mod usestate2;
// pub use usestate2::use_state2;
mod useref;
pub use useref::*;
@@ -9,3 +12,6 @@ pub use use_shared_state::*;
mod 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>>;
// Tracks all the subscribers to a shared State
pub(crate) struct ProvidedStateInner<T> {
pub struct ProvidedStateInner<T> {
value: Rc<RefCell<T>>,
notify_any: Rc<dyn Fn(ScopeId)>,
consumers: HashSet<ScopeId>,
@@ -20,6 +20,14 @@ impl<T> ProvidedStateInner<T> {
(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.
@@ -114,10 +122,10 @@ impl<'a, T: 'static> UseSharedState<'a, T> {
}
pub fn notify_consumers(self) {
// if !self.needs_notification.get() {
self.root.borrow_mut().notify_consumers();
// self.needs_notification.set(true);
// }
if !self.needs_notification.get() {
self.root.borrow_mut().notify_consumers();
self.needs_notification.set(true);
}
}
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> {
self.value.borrow_mut()
}
pub fn inner(&self) -> Rc<RefCell<ProvidedStateInner<T>>> {
self.root.clone()
}
}
impl<T> Copy for UseSharedState<'_, T> {}
+17 -3
View File
@@ -6,9 +6,9 @@ use std::{
rc::Rc,
};
pub fn use_coroutine<'a, F: Future<Output = ()> + 'a>(
pub fn use_coroutine<'a, F: Future<Output = ()> + 'static>(
cx: Context<'a>,
f: impl FnOnce() -> F + 'a,
mut f: impl FnMut() -> F + 'a,
) -> CoroutineHandle {
//
cx.use_hook(
@@ -59,10 +59,19 @@ pub struct CoroutineHandle<'a> {
cx: Context<'a>,
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> {
pub fn start(&self) {
if self.inner.running.get() {
if self.is_running() {
return;
}
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());
}
}
pub fn is_running(&self) -> bool {
self.inner.running.get()
}
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> {
cx.use_hook(
|_| UseRefInner {
update_scheuled: Cell::new(false),
update_scheduled: Cell::new(false),
update_callback: cx.schedule_update(),
value: RefCell::new(f()),
},
|inner| {
inner.update_scheuled.set(false);
inner.update_scheduled.set(false);
UseRef { inner }
},
)
@@ -23,7 +23,7 @@ pub struct UseRef<'a, T> {
inner: &'a UseRefInner<T>,
}
struct UseRefInner<T> {
update_scheuled: Cell<bool>,
update_scheduled: Cell<bool>,
update_callback: Rc<dyn Fn()>,
value: RefCell<T>,
}
@@ -54,8 +54,8 @@ impl<'a, T> UseRef<'a, T> {
}
pub fn needs_update(&self) {
if !self.inner.update_scheuled.get() {
self.inner.update_scheuled.set(true);
if !self.inner.update_scheduled.get() {
self.inner.update_scheduled.set(true);
(self.inner.update_callback)();
}
}
+34 -7
View File
@@ -8,7 +8,7 @@ use std::{
/// 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
/// 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> {
cx.use_hook(
move |_| {
//
let first_val = initial_state_fn();
UseStateInner {
current_val: initial_state_fn(),
current_val: Rc::new(first_val),
update_callback: cx.schedule_update(),
wip: Rc::new(RefCell::new(None)),
update_scheuled: Cell::new(false),
@@ -65,9 +65,15 @@ pub fn use_state<'a, T: 'static>(
},
move |hook| {
hook.update_scheuled.set(false);
let mut new_val = hook.wip.borrow_mut();
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 }
@@ -75,7 +81,7 @@ pub fn use_state<'a, T: 'static>(
)
}
struct UseStateInner<T: 'static> {
current_val: T,
current_val: Rc<T>,
update_scheuled: Cell<bool>,
update_callback: Rc<dyn Fn()>,
wip: Rc<RefCell<Option<T>>>,
@@ -117,6 +123,10 @@ impl<'a, T: 'static> UseState<'a, T> {
&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
pub fn get_wip(&self) -> Ref<Option<T>> {
self.inner.wip.borrow()
@@ -134,14 +144,15 @@ impl<'a, T: 'static> UseState<'a, T> {
pub fn setter(&self) -> Rc<dyn Fn(T)> {
let slot = self.inner.wip.clone();
Rc::new(move |new| {
//
*slot.borrow_mut() = Some(new);
})
}
pub fn for_async(&self) -> AsyncUseState<T> {
AsyncUseState {
re_render: self.inner.update_callback.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
RefMut::map(self.inner.wip.borrow_mut(), |slot| {
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()
})
}
pub fn inner(self) -> T {
self.inner.current_val.as_ref().to_owned()
}
}
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
pub struct AsyncUseState<T: 'static> {
inner: Rc<T>,
re_render: Rc<dyn Fn()>,
wip: Rc<RefCell<Option<T>>>,
}
@@ -269,7 +286,17 @@ impl<T: ToOwned> AsyncUseState<T> {
slot.as_mut().unwrap()
})
}
}
impl<T> AsyncUseState<T> {
pub fn set(&mut self, val: T) {
(self.re_render)();
*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
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");
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)
}
pub fn launch_with_props<P: 'static + Send>(
root: FC<P>,
root: Component<P>,
props: P,
builder: fn(WindowBuilder) -> WindowBuilder,
) -> 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.
pub struct WebviewRenderer<T> {
/// The root component used to render the Webview
root: FC<T>,
root: Component<T>,
}
enum RpcEvent<'a> {
Initialize {
@@ -52,7 +55,7 @@ enum RpcEvent<'a> {
impl<T: 'static + Send> WebviewRenderer<T> {
pub fn run(
root: FC<T>,
root: Component<T>,
props: T,
user_builder: fn(WindowBuilder) -> WindowBuilder,
) -> anyhow::Result<()> {
@@ -60,7 +63,7 @@ impl<T: 'static + Send> WebviewRenderer<T> {
}
pub fn run_with_edits(
root: FC<T>,
root: Component<T>,
props: T,
user_builder: fn(WindowBuilder) -> WindowBuilder,
redits: Option<Vec<DomEdit<'static>>>,
+1 -1
View File
@@ -9,7 +9,7 @@ fn main() {
dioxus_web::launch(App, |c| c);
}
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
#[derive(Clone, Debug, PartialEq)]
enum Route {
Home,
+5 -5
View File
@@ -301,13 +301,13 @@ mod tests {
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
static SIMPLE_APP: FC<()> = |cx, _| {
static SIMPLE_APP: Component<()> = |cx, _| {
cx.render(rsx!(div {
"hello world!"
}))
};
static SLIGHTLY_MORE_COMPLEX: FC<()> = |cx, _| {
static SLIGHTLY_MORE_COMPLEX: Component<()> = |cx, _| {
cx.render(rsx! {
div {
title: "About W3Schools"
@@ -326,14 +326,14 @@ mod tests {
})
};
static NESTED_APP: FC<()> = |cx, _| {
static NESTED_APP: Component<()> = |cx, _| {
cx.render(rsx!(
div {
SIMPLE_APP {}
}
))
};
static FRAGMENT_APP: FC<()> = |cx, _| {
static FRAGMENT_APP: Component<()> = |cx, _| {
cx.render(rsx!(
div { "f1" }
div { "f2" }
@@ -389,7 +389,7 @@ mod tests {
#[test]
fn styles() {
static STLYE_APP: FC<()> = |cx, _| {
static STLYE_APP: Component<()> = |cx, _| {
cx.render(rsx! {
div { color: "blue", font_size: "46px" }
})
+1 -1
View File
@@ -14,7 +14,7 @@ fn main() {
dioxus_web::launch(App, |c| c);
}
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
let mut count = use_state(cx, || 0);
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 selected = use_state(cx, || None);
+1 -1
View File
@@ -15,7 +15,7 @@ fn main() {
dioxus_web::launch(App, |c| c);
}
static App: FC<()> = |cx, props| {
static App: Component<()> = |cx, props| {
let show = use_state(cx, || true);
let inner = match *show {
+1 -1
View File
@@ -14,7 +14,7 @@ fn main() {
dioxus_web::launch(App, |c| c);
}
static App: FC<()> = |cx, _| {
static App: Component<()> = |cx, _| {
let doggo = cx.suspend(|| async move {
#[derive(serde::Deserialize)]
struct Doggo {
+298 -311
View File
@@ -1,311 +1,298 @@
/// Wasm-bindgen has a performance option to intern commonly used phrases
/// This saves the decoding cost, making the interaction of Rust<->JS more performant.
/// We intern all the HTML tags and attributes, making most operations much faster.
///
/// Interning takes < 1ms at the start of the app, but saves a *ton* of time later on.
///
/// Eventually we might want to procedurally generate these strings for common words, phrases, and values.
pub(crate) fn intern_cached_strings() {
let cached_words = [
// Important tags to dioxus
"dioxus-id",
"dioxus",
"dioxus-event-click", // todo: more events
"click",
// All the HTML Tags
"a",
"abbr",
"address",
"area",
"article",
"aside",
"audio",
"b",
"base",
"bdi",
"bdo",
"big",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"cite",
"code",
"col",
"colgroup",
"command",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"div",
"dl",
"dt",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hr",
"html",
"i",
"iframe",
"img",
"input",
"ins",
"kbd",
"keygen",
"label",
"legend",
"li",
"link",
"main",
"map",
"mark",
"menu",
"menuitem",
"meta",
"meter",
"nav",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"param",
"picture",
"pre",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"small",
"source",
"span",
"strong",
"style",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"u",
"ul",
"var",
"video",
"wbr",
// All the event handlers
"Attribute",
"accept",
"accept-charset",
"accesskey",
"action",
"alt",
"async",
"autocomplete",
"autofocus",
"autoplay",
"charset",
"checked",
"cite",
"class",
"cols",
"colspan",
"content",
"contenteditable",
"controls",
"coords",
"data",
"data-*",
"datetime",
"default",
"defer",
"dir",
"dirname",
"disabled",
"download",
"draggable",
"enctype",
"for",
"form",
"formaction",
"headers",
"height",
"hidden",
"high",
"href",
"hreflang",
"http-equiv",
"id",
"ismap",
"kind",
"label",
"lang",
"list",
"loop",
"low",
"max",
"maxlength",
"media",
"method",
"min",
"multiple",
"muted",
"name",
"novalidate",
"onabort",
"onafterprint",
"onbeforeprint",
"onbeforeunload",
"onblur",
"oncanplay",
"oncanplaythrough",
"onchange",
"onclick",
"oncontextmenu",
"oncopy",
"oncuechange",
"oncut",
"ondblclick",
"ondrag",
"ondragend",
"ondragenter",
"ondragleave",
"ondragover",
"ondragstart",
"ondrop",
"ondurationchange",
"onemptied",
"onended",
"onerror",
"onfocus",
"onhashchange",
"oninput",
"oninvalid",
"onkeydown",
"onkeypress",
"onkeyup",
"onload",
"onloadeddata",
"onloadedmetadata",
"onloadstart",
"onmousedown",
"onmousemove",
"onmouseout",
"onmouseover",
"onmouseup",
"onmousewheel",
"onoffline",
"ononline",
"onpageshow",
"onpaste",
"onpause",
"onplay",
"onplaying",
"onprogress",
"onratechange",
"onreset",
"onresize",
"onscroll",
"onsearch",
"onseeked",
"onseeking",
"onselect",
"onstalled",
"onsubmit",
"onsuspend",
"ontimeupdate",
"ontoggle",
"onunload",
"onvolumechange",
"onwaiting",
"onwheel",
"open",
"optimum",
"pattern",
"placeholder",
"poster",
"preload",
"readonly",
"rel",
"required",
"reversed",
"rows",
"rowspan",
"sandbox",
"scope",
"selected",
"shape",
"size",
"sizes",
"span",
"spellcheck",
"src",
"srcdoc",
"srclang",
"srcset",
"start",
"step",
"style",
"tabindex",
"target",
"title",
"translate",
"type",
"usemap",
"value",
"width",
"wrap",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
];
for s in cached_words {
wasm_bindgen::intern(s);
}
}
pub static BUILTIN_INTERNED_STRINGS: &[&'static str] = &[
// Important tags to dioxus
"dioxus-id",
"dioxus",
"dioxus-event-click", // todo: more events
"click",
// All the HTML Tags
"a",
"abbr",
"address",
"area",
"article",
"aside",
"audio",
"b",
"base",
"bdi",
"bdo",
"big",
"blockquote",
"body",
"br",
"button",
"canvas",
"caption",
"cite",
"code",
"col",
"colgroup",
"command",
"data",
"datalist",
"dd",
"del",
"details",
"dfn",
"dialog",
"div",
"dl",
"dt",
"em",
"embed",
"fieldset",
"figcaption",
"figure",
"footer",
"form",
"h1",
"h2",
"h3",
"h4",
"h5",
"h6",
"head",
"header",
"hr",
"html",
"i",
"iframe",
"img",
"input",
"ins",
"kbd",
"keygen",
"label",
"legend",
"li",
"link",
"main",
"map",
"mark",
"menu",
"menuitem",
"meta",
"meter",
"nav",
"noscript",
"object",
"ol",
"optgroup",
"option",
"output",
"p",
"param",
"picture",
"pre",
"progress",
"q",
"rp",
"rt",
"ruby",
"s",
"samp",
"script",
"section",
"select",
"small",
"source",
"span",
"strong",
"style",
"sub",
"summary",
"sup",
"table",
"tbody",
"td",
"textarea",
"tfoot",
"th",
"thead",
"time",
"title",
"tr",
"track",
"u",
"ul",
"var",
"video",
"wbr",
// All the event handlers
"Attribute",
"accept",
"accept-charset",
"accesskey",
"action",
"alt",
"async",
"autocomplete",
"autofocus",
"autoplay",
"charset",
"checked",
"cite",
"class",
"cols",
"colspan",
"content",
"contenteditable",
"controls",
"coords",
"data",
"data-*",
"datetime",
"default",
"defer",
"dir",
"dirname",
"disabled",
"download",
"draggable",
"enctype",
"for",
"form",
"formaction",
"headers",
"height",
"hidden",
"high",
"href",
"hreflang",
"http-equiv",
"id",
"ismap",
"kind",
"label",
"lang",
"list",
"loop",
"low",
"max",
"maxlength",
"media",
"method",
"min",
"multiple",
"muted",
"name",
"novalidate",
"onabort",
"onafterprint",
"onbeforeprint",
"onbeforeunload",
"onblur",
"oncanplay",
"oncanplaythrough",
"onchange",
"onclick",
"oncontextmenu",
"oncopy",
"oncuechange",
"oncut",
"ondblclick",
"ondrag",
"ondragend",
"ondragenter",
"ondragleave",
"ondragover",
"ondragstart",
"ondrop",
"ondurationchange",
"onemptied",
"onended",
"onerror",
"onfocus",
"onhashchange",
"oninput",
"oninvalid",
"onkeydown",
"onkeypress",
"onkeyup",
"onload",
"onloadeddata",
"onloadedmetadata",
"onloadstart",
"onmousedown",
"onmousemove",
"onmouseout",
"onmouseover",
"onmouseup",
"onmousewheel",
"onoffline",
"ononline",
"onpageshow",
"onpaste",
"onpause",
"onplay",
"onplaying",
"onprogress",
"onratechange",
"onreset",
"onresize",
"onscroll",
"onsearch",
"onseeked",
"onseeking",
"onselect",
"onstalled",
"onsubmit",
"onsuspend",
"ontimeupdate",
"ontoggle",
"onunload",
"onvolumechange",
"onwaiting",
"onwheel",
"open",
"optimum",
"pattern",
"placeholder",
"poster",
"preload",
"readonly",
"rel",
"required",
"reversed",
"rows",
"rowspan",
"sandbox",
"scope",
"selected",
"shape",
"size",
"sizes",
"span",
"spellcheck",
"src",
"srcdoc",
"srclang",
"srcset",
"start",
"step",
"style",
"tabindex",
"target",
"title",
"translate",
"type",
"usemap",
"value",
"width",
"wrap",
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"10",
"11",
"12",
"13",
"14",
];
+10
View File
@@ -11,6 +11,7 @@
pub struct WebConfig {
pub(crate) hydrate: bool,
pub(crate) rootname: String,
pub(crate) cached_strings: Vec<String>,
}
impl Default for WebConfig {
@@ -18,6 +19,7 @@ impl Default for WebConfig {
Self {
hydrate: false,
rootname: "main".to_string(),
cached_strings: Vec::new(),
}
}
}
@@ -41,4 +43,12 @@ impl WebConfig {
self.rootname = name.into();
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;
use crate::dom::load_document;
use cache::intern_cached_strings;
use dioxus::SchedulerMsg;
use dioxus::VirtualDom;
pub use dioxus_core as dioxus;
use dioxus_core::prelude::FC;
use dioxus_core::prelude::Component;
use futures_util::FutureExt;
mod cache;
@@ -82,15 +81,15 @@ mod ric_raf;
///
/// ```rust
/// 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"})
/// }
/// ```
pub fn launch(root_component: FC<()>, configuration: impl FnOnce(WebConfig) -> WebConfig) {
launch_with_props(root_component, (), configuration)
pub fn launch(root_component: Component<()>) {
launch_with_props(root_component, (), |c| c);
}
/// 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
/// }
///
/// static App: FC<RootProps> = |cx, props| {
/// static App: Component<RootProps> = |cx, props| {
/// rsx!(cx, div {"hello {props.name}"})
/// }
/// ```
pub fn launch_with_props<T, F>(root_component: FC<T>, root_properties: T, configuration_builder: F)
where
pub fn launch_with_props<T, F>(
root_component: Component<T>,
root_properties: T,
configuration_builder: F,
) where
T: Send + 'static,
F: FnOnce(WebConfig) -> WebConfig,
{
@@ -134,10 +136,15 @@ where
/// 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);
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;
@@ -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
// ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
if !should_hydrate {
// log::info!("Applying rebuild edits..., {:?}", mutations);
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.
dom.wait_for_work().await;
// // wait for the mainthread to schedule us in
// let mut deadline = work_loop.wait_for_idle_time().await;
// wait for the mainthread to schedule us in
let mut deadline = work_loop.wait_for_idle_time().await;
// run the virtualdom work phase until the frame deadline is reached
let mutations = dom.work_with_deadline(|| false);
// // run the virtualdom work phase until the frame deadline is reached
// let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
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
work_loop.wait_for_raf().await;
for mut edit in mutations {
// actually apply our changes during the animation frame
// log::info!("Applying change edits..., {:?}", edit);
websys_dom.process_edits(&mut edit.edits);
}
}