polish: clean up the core crate

This commit is contained in:
Jonathan Kelley 2021-12-12 19:47:13 -05:00
parent 1e4a599d14
commit e6c6bbdc1e
15 changed files with 760 additions and 434 deletions

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> {

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::*;
@ -37,7 +26,7 @@ pub(crate) mod innerlude {
pub use crate::innerlude::{
Attribute, Component, Context, DioxusElement, DomEdit, Element, ElementId, EventHandler,
EventPriority, IntoVNode, LazyNodes, Listener, MountType, Mutations, NodeFactory, Properties,
EventPriority, IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties,
SchedulerMsg, ScopeId, UserEvent, VElement, VFragment, VNode, VirtualDom,
};

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(),
}
}

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.
@ -287,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,
{
@ -303,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
}
@ -335,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
@ -380,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
@ -416,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],
@ -423,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],
@ -433,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
}
}
@ -455,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 {
@ -468,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();

View file

@ -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;

View file

@ -183,6 +183,11 @@ 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: Component<P>,
root_props: P,
@ -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();
@ -486,6 +529,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 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);

View file

@ -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!(

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::*;

View file

@ -59,6 +59,15 @@ 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) {

View file

@ -54,18 +54,26 @@ pub fn use_state<'a, T: 'static>(
initial_state_fn: impl FnOnce() -> T,
) -> UseState<'a, T> {
cx.use_hook(
move |_| UseStateInner {
current_val: initial_state_fn(),
update_callback: cx.schedule_update(),
wip: Rc::new(RefCell::new(None)),
update_scheuled: Cell::new(false),
move |_| {
let first_val = initial_state_fn();
UseStateInner {
current_val: Rc::new(first_val),
update_callback: cx.schedule_update(),
wip: Rc::new(RefCell::new(None)),
update_scheuled: Cell::new(false),
}
},
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 }
@ -73,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>>>,
@ -115,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()
@ -140,6 +152,7 @@ impl<'a, T: 'static> UseState<'a, T> {
AsyncUseState {
re_render: self.inner.update_callback.clone(),
wip: self.inner.wip.clone(),
inner: self.inner.current_val.clone(),
}
}
@ -162,14 +175,14 @@ 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.to_owned()
self.inner.current_val.as_ref().to_owned()
}
}
@ -256,6 +269,7 @@ 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>>>,
}
@ -278,4 +292,11 @@ impl<T> AsyncUseState<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
}
}

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)
// }
// }

View file

@ -22,3 +22,28 @@ async fn main() -> tide::Result<()> {
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" }
))
}
```

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",
];

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
}
}

View file

@ -56,7 +56,6 @@ 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;
@ -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: Component<()>, 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,7 +108,7 @@ pub fn launch(root_component: Component<()>, configuration: impl FnOnce(WebConfi
/// name: String
/// }
///
/// static App: FC<RootProps> = |cx, props| {
/// static App: Component<RootProps> = |cx, props| {
/// rsx!(cx, div {"hello {props.name}"})
/// }
/// ```
@ -140,7 +139,12 @@ pub fn launch_with_props<T, F>(
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;
@ -158,7 +162,6 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
// 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);
}
@ -169,20 +172,17 @@ pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T
// 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);
}
}