polish: clean up the core crate

This commit is contained in:
Jonathan Kelley committed 2021-12-12 19:47:13 -05:00
1 parent 1e4a599d14
commit e6c6bbdc1e
15 files changed
+459 -133

No files matched your search

+8 -8
View File
@@ -105,11 +105,11 @@ use DomEdit::*;
/// ///
/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's /// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's
/// stack machines all the way down! /// stack machines all the way down!
pub struct DiffState<'bump> { pub(crate) struct DiffState<'bump> {
scopes: &'bump ScopeArena, pub(crate) scopes: &'bump ScopeArena,
pub mutations: Mutations<'bump>, pub(crate) mutations: Mutations<'bump>,
pub(crate) stack: DiffStack<'bump>, pub(crate) stack: DiffStack<'bump>,
pub force_diff: bool, pub(crate) force_diff: bool,
} }
impl<'bump> DiffState<'bump> { impl<'bump> DiffState<'bump> {
@@ -149,7 +149,7 @@ pub(crate) enum DiffInstruction<'a> {
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum MountType<'a> { pub(crate) enum MountType<'a> {
Absorb, Absorb,
Append, Append,
Replace { old: &'a VNode<'a> }, Replace { old: &'a VNode<'a> },
@@ -159,9 +159,9 @@ pub enum MountType<'a> {
pub(crate) struct DiffStack<'bump> { pub(crate) struct DiffStack<'bump> {
pub(crate) instructions: Vec<DiffInstruction<'bump>>, pub(crate) instructions: Vec<DiffInstruction<'bump>>,
nodes_created_stack: SmallVec<[usize; 10]>, pub(crate) nodes_created_stack: SmallVec<[usize; 10]>,
pub scope_stack: SmallVec<[ScopeId; 5]>, pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
pub element_stack: SmallVec<[ElementId; 10]>, pub(crate) element_stack: SmallVec<[ElementId; 10]>,
} }
impl<'bump> DiffStack<'bump> { impl<'bump> DiffStack<'bump> {
+2 -13
View File
@@ -1,17 +1,6 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
#![doc = include_str!("../README.md")] #![doc = include_str!("../README.md")]
/*
Navigating this crate:
- virtual_dom: the primary entrypoint for the crate
- scheduler: the core interior logic called by the [`VirtualDom`]
- nodes: the definition of VNodes, listeners, etc.
- diff: the stackmachine-based diffing algorithm
- hooks: foundational hooks that require crate-private APIs
- mutations: DomEdits/NodeRefs and internal API to create them
Some utilities
*/
pub(crate) mod component; pub(crate) mod component;
pub(crate) mod diff; pub(crate) mod diff;
pub(crate) mod lazynodes; pub(crate) mod lazynodes;
@@ -23,7 +12,7 @@ pub(crate) mod virtual_dom;
pub(crate) mod innerlude { pub(crate) mod innerlude {
pub use crate::component::*; pub use crate::component::*;
pub use crate::diff::*; pub(crate) use crate::diff::*;
pub use crate::lazynodes::*; pub use crate::lazynodes::*;
pub use crate::mutations::*; pub use crate::mutations::*;
pub use crate::nodes::*; pub use crate::nodes::*;
@@ -37,7 +26,7 @@ pub(crate) mod innerlude {
pub use crate::innerlude::{ pub use crate::innerlude::{
Attribute, Component, Context, DioxusElement, DomEdit, Element, ElementId, EventHandler, 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, SchedulerMsg, ScopeId, UserEvent, VElement, VFragment, VNode, VirtualDom,
}; };
-2
View File
@@ -16,7 +16,6 @@ pub struct Mutations<'a> {
pub edits: Vec<DomEdit<'a>>, pub edits: Vec<DomEdit<'a>>,
pub dirty_scopes: FxHashSet<ScopeId>, pub dirty_scopes: FxHashSet<ScopeId>,
pub refs: Vec<NodeRefMutation<'a>>, pub refs: Vec<NodeRefMutation<'a>>,
pub effects: Vec<&'a dyn FnMut()>,
} }
impl Debug for Mutations<'_> { impl Debug for Mutations<'_> {
@@ -113,7 +112,6 @@ impl<'a> Mutations<'a> {
edits: Vec::new(), edits: Vec::new(),
refs: Vec::new(), refs: Vec::new(),
dirty_scopes: Default::default(), dirty_scopes: Default::default(),
effects: Vec::new(),
} }
} }
+49 -50
View File
@@ -7,6 +7,7 @@ use std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
collections::HashMap, collections::HashMap,
future::Future, future::Future,
pin::Pin,
rc::Rc, rc::Rc,
}; };
@@ -32,6 +33,14 @@ use bumpalo::{boxed::Box as BumpBox, Bump};
/// ``` /// ```
pub type Context<'a> = &'a Scope; pub type Context<'a> = &'a Scope;
/// A component's unique identifier.
///
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM and across time. ScopeIDs will never be reused
/// once a component has been unmounted.
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ScopeId(pub usize);
/// Every component in Dioxus is represented by a `Scope`. /// Every component in Dioxus is represented by a `Scope`.
/// ///
/// Scopes contain the state for hooks, the component's props, and other lifecycle information. /// Scopes contain the state for hooks, the component's props, and other lifecycle information.
@@ -44,7 +53,6 @@ pub type Context<'a> = &'a Scope;
pub struct Scope { pub struct Scope {
pub(crate) parent_scope: Option<*mut Scope>, pub(crate) parent_scope: Option<*mut Scope>,
// parent element I think?
pub(crate) container: ElementId, pub(crate) container: ElementId,
pub(crate) our_arena_idx: ScopeId, pub(crate) our_arena_idx: ScopeId,
@@ -75,17 +83,9 @@ pub struct Scope {
pub struct SelfReferentialItems<'a> { pub struct SelfReferentialItems<'a> {
pub(crate) listeners: Vec<&'a Listener<'a>>, pub(crate) listeners: Vec<&'a Listener<'a>>,
pub(crate) borrowed_props: Vec<&'a VComponent<'a>>, pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>, pub(crate) tasks: Vec<Pin<BumpBox<'a, dyn Future<Output = ()>>>>,
} }
/// A component's unique identifier.
///
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM and across time. ScopeIDs will never be reused
/// once a component has been unmounted.
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ScopeId(pub usize);
// Public methods exposed to libraries and components // Public methods exposed to libraries and components
impl Scope { impl Scope {
/// Get the subtree ID that this scope belongs to. /// Get the subtree ID that this scope belongs to.
@@ -227,11 +227,11 @@ impl Scope {
let _ = self.sender.unbounded_send(SchedulerMsg::Immediate(id)); let _ = self.sender.unbounded_send(SchedulerMsg::Immediate(id));
} }
/// Get the [`ScopeId`] of a mounted component. /// Get the Root Node of this scope
/// pub fn root_node(&self) -> &VNode {
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted. todo!("Portals have changed how we address nodes. Still fixing this, sorry.");
pub fn bump(&self) -> &Bump { // let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
&self.wip_frame().bump // unsafe { std::mem::transmute(&*node) }
} }
/// This method enables the ability to expose state to children further down the VirtualDOM Tree. /// This method enables the ability to expose state to children further down the VirtualDOM Tree.
@@ -287,15 +287,10 @@ impl Scope {
/// Pushes the future onto the poll queue to be polled after the component renders. /// Pushes the future onto the poll queue to be polled after the component renders.
/// ///
///
///
///
/// The future is forcibly dropped if the component is not ready by the next render /// The future is forcibly dropped if the component is not ready by the next render
pub fn push_task<'src, F: Future<Output = ()>>( pub fn push_task<'src, F>(&'src self, fut: impl FnOnce() -> F + 'src) -> usize
&'src self,
fut: impl FnOnce() -> F + 'src,
) -> usize
where where
F: Future<Output = ()>,
F::Output: 'src, F::Output: 'src,
F: 'src, F: 'src,
{ {
@@ -303,18 +298,20 @@ impl Scope {
.unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx)) .unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
.unwrap(); .unwrap();
// allocate the future
let fut = fut();
let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut);
// wrap it in a type that will actually drop the contents // wrap it in a type that will actually drop the contents
//
// Safety: we just made the pointer above and will promise not to alias it!
// The main reason we do this through from_raw is because Bumpalo's box does
// not support unsized coercion
let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut());
let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) }; let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) };
let pinned_fut: Pin<BumpBox<_>> = boxed_fut.into();
// erase the 'src lifetime for self-referential storage // erase the 'src lifetime for self-referential storage
let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) }; let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) };
// Push the future into the tasks
let mut items = self.items.borrow_mut(); let mut items = self.items.borrow_mut();
items.tasks.push(self_ref_fut); items.tasks.push(self_ref_fut);
items.tasks.len() - 1 items.tasks.len() - 1
} }
@@ -335,22 +332,19 @@ impl Scope {
/// } /// }
///``` ///```
pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<VPortal> { pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<VPortal> {
let frame = self.wip_frame(); let bump = &self.wip_frame().bump;
let bump = &frame.bump;
let factory = NodeFactory { bump };
let node = rsx.map(|f| f.call(factory))?;
let node = bump.alloc(node);
let node_ptr = node as *mut _; let owned_node: VNode<'src> = rsx.map(|f| f.call(NodeFactory { bump }))?;
let node_ptr = unsafe { std::mem::transmute(node_ptr) }; let alloced_vnode: &'src mut VNode<'src> = bump.alloc(owned_node);
let node_ptr: *mut VNode<'src> = alloced_vnode as *mut _;
let link = VPortal { let node: *mut VNode<'static> = unsafe { std::mem::transmute(node_ptr) };
Some(VPortal {
scope_id: Cell::new(Some(self.our_arena_idx)), scope_id: Cell::new(Some(self.our_arena_idx)),
link_idx: Cell::new(0), link_idx: Cell::new(0),
node: node_ptr, node,
}; })
Some(link)
} }
/// Store a value between renders /// Store a value between renders
@@ -380,12 +374,12 @@ impl Scope {
runner: impl FnOnce(&'src mut State) -> Output, runner: impl FnOnce(&'src mut State) -> Output,
) -> Output { ) -> Output {
let mut vals = self.hook_vals.borrow_mut(); let mut vals = self.hook_vals.borrow_mut();
let hook_len = vals.len(); let hook_len = vals.len();
let cur_idx = self.hook_idx.get(); let cur_idx = self.hook_idx.get();
if cur_idx >= hook_len { if cur_idx >= hook_len {
let val = self.hook_arena.alloc(initializer(hook_len)); vals.push(self.hook_arena.alloc(initializer(hook_len)));
vals.push(val);
} }
let state = vals let state = vals
@@ -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 { pub(crate) fn wip_frame_mut(&mut self) -> &mut BumpFrame {
match self.generation.get() & 1 == 0 { match self.generation.get() & 1 == 0 {
true => &mut self.frames[0], true => &mut self.frames[0],
@@ -423,6 +418,7 @@ impl Scope {
} }
} }
/// Access to the frame where finalized nodes existed
pub(crate) fn fin_frame(&self) -> &BumpFrame { pub(crate) fn fin_frame(&self) -> &BumpFrame {
match self.generation.get() & 1 == 1 { match self.generation.get() & 1 == 1 {
true => &self.frames[0], true => &self.frames[0],
@@ -433,20 +429,23 @@ impl Scope {
/// Reset this component's frame /// Reset this component's frame
/// ///
/// # Safety: /// # Safety:
///
/// This method breaks every reference of VNodes in the current frame. /// This method breaks every reference of VNodes in the current frame.
///
/// Calling reset itself is not usually a big deal, but we consider it important
/// due to the complex safety guarantees we need to uphold.
pub(crate) unsafe fn reset_wip_frame(&mut self) { pub(crate) unsafe fn reset_wip_frame(&mut self) {
// todo: unsafecell or something self.wip_frame_mut().bump.reset();
let bump = self.wip_frame_mut();
bump.bump.reset();
} }
/// Cycle to the next generation
pub(crate) fn cycle_frame(&self) { pub(crate) fn cycle_frame(&self) {
self.generation.set(self.generation.get() + 1); self.generation.set(self.generation.get() + 1);
} }
pub fn root_node(&self) -> &VNode { /// Get the [`Bump`] of the WIP frame.
let node = *self.wip_frame().nodes.borrow().get(0).unwrap(); pub(crate) fn bump(&self) -> &Bump {
unsafe { std::mem::transmute(&*node) } &self.wip_frame().bump
} }
} }
@@ -455,7 +454,7 @@ pub(crate) struct BumpFrame {
pub nodes: RefCell<Vec<*const VNode<'static>>>, pub nodes: RefCell<Vec<*const VNode<'static>>>,
} }
impl BumpFrame { impl BumpFrame {
pub fn new(capacity: usize) -> Self { pub(crate) fn new(capacity: usize) -> Self {
let bump = Bump::with_capacity(capacity); let bump = Bump::with_capacity(capacity);
let node = &*bump.alloc(VText { let node = &*bump.alloc(VText {
@@ -468,7 +467,7 @@ impl BumpFrame {
Self { bump, nodes } Self { bump, nodes }
} }
pub fn assign_nodelink(&self, node: &VPortal) { pub(crate) fn assign_nodelink(&self, node: &VPortal) {
let mut nodes = self.nodes.borrow_mut(); let mut nodes = self.nodes.borrow_mut();
let len = nodes.len(); let len = nodes.len();
-7
View File
@@ -92,15 +92,8 @@ impl ScopeArena {
let new_scope_id = ScopeId(self.scope_counter.get()); let new_scope_id = ScopeId(self.scope_counter.get());
self.scope_counter.set(self.scope_counter.get() + 1); self.scope_counter.set(self.scope_counter.get() + 1);
// log::debug!("new scope {:?} with parent {:?}", new_scope_id, container);
if let Some(old_scope) = self.free_scopes.borrow_mut().pop() { if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
let scope = unsafe { &mut *old_scope }; let scope = unsafe { &mut *old_scope };
// log::debug!(
// "reusing scope {:?} as {:?}",
// scope.our_arena_idx,
// new_scope_id
// );
scope.caller = caller; scope.caller = caller;
scope.parent_scope = parent_scope; scope.parent_scope = parent_scope;
+83 -18
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 /// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
/// VirtualDom to be created just to retrieve its channel receiver. /// VirtualDom to be created just to retrieve its channel receiver.
///
/// ```rust
/// let (sender, receiver) = futures_channel::mpsc::unbounded();
/// let dom = VirtualDom::new_with_scheduler(Example, (), sender, receiver);
/// ```
pub fn new_with_props_and_scheduler<P: 'static>( pub fn new_with_props_and_scheduler<P: 'static>(
root: Component<P>, root: Component<P>,
root_props: P, root_props: P,
@@ -235,27 +240,55 @@ impl VirtualDom {
/// # Example /// # Example
/// ///
/// ```rust, ignore /// ```rust, ignore
/// /// let dom = VirtualDom::new(App);
/// /// let sender = dom.get_scheduler_channel();
/// ``` /// ```
pub fn get_scheduler_channel(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> { pub fn get_scheduler_channel(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
self.sender.clone() self.sender.clone()
} }
/// Add a new message to the scheduler queue directly.
///
///
/// This method makes it possible to send messages to the scheduler from outside the VirtualDom without having to
/// call `get_schedule_channel` and then `send`.
///
/// # Example
/// ```rust, ignore
/// let dom = VirtualDom::new(App);
/// dom.insert_scheduler_message(SchedulerMsg::Immediate(ScopeId(0)));
/// ```
pub fn insert_scheduler_message(&self, msg: SchedulerMsg) {
self.sender.unbounded_send(msg).unwrap()
}
/// Check if the [`VirtualDom`] has any pending updates or work to be done. /// Check if the [`VirtualDom`] has any pending updates or work to be done.
/// ///
/// # Example /// # Example
/// ///
/// ```rust, ignore /// ```rust, ignore
/// let dom = VirtualDom::new(App);
/// ///
/// /// // the dom is "dirty" when it is started and must be rebuilt to get the first render
/// assert!(dom.has_any_work());
/// ``` /// ```
pub fn has_any_work(&self) -> bool { pub fn has_work(&self) -> bool {
!(self.dirty_scopes.is_empty() && self.pending_messages.is_empty()) !(self.dirty_scopes.is_empty() && self.pending_messages.is_empty())
} }
/// Waits for the scheduler to have work /// Wait for the scheduler to have any work.
///
/// This method polls the internal future queue *and* the scheduler channel.
/// To add work to the VirtualDom, insert a message via the scheduler channel.
///
/// This lets us poll async tasks during idle periods without blocking the main thread. /// This lets us poll async tasks during idle periods without blocking the main thread.
///
/// # Example
///
/// ```rust, ignore
/// let dom = VirtualDom::new(App);
/// let sender = dom.get_scheduler_channel();
/// ```
pub async fn wait_for_work(&mut self) { pub async fn wait_for_work(&mut self) {
loop { loop {
if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() { if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() {
@@ -390,9 +423,10 @@ impl VirtualDom {
committed_mutations.push(mutations); committed_mutations.push(mutations);
} else { } else {
log::debug!("Could not finish work in time");
// leave the work in an incomplete state // leave the work in an incomplete state
//
// todo: we should store the edits and re-apply them later
// for now, we just dump the work completely (threadsafe)
return committed_mutations; return committed_mutations;
} }
} }
@@ -400,7 +434,7 @@ impl VirtualDom {
committed_mutations committed_mutations
} }
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
/// ///
/// The diff machine expects the RealDom's stack to be the root of the application. /// The diff machine expects the RealDom's stack to be the root of the application.
/// ///
@@ -475,6 +509,15 @@ impl VirtualDom {
/// Renders an `rsx` call into the Base Scope's allocator. /// Renders an `rsx` call into the Base Scope's allocator.
/// ///
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test. /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
///
/// ```rust
/// fn Base(cx: Context, props: &()) -> Element {
/// rsx!(cx, div {})
/// }
///
/// let dom = VirtualDom::new(Base);
/// let nodes = dom.render_nodes(rsx!("div"));
/// ```
pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> &'a VNode<'a> { pub fn render_vnodes<'a>(&'a self, lazy_nodes: Option<LazyNodes<'a, '_>>) -> &'a VNode<'a> {
let scope = self.scopes.get_scope(&self.base_scope).unwrap(); let scope = self.scopes.get_scope(&self.base_scope).unwrap();
let frame = scope.wip_frame(); let frame = scope.wip_frame();
@@ -486,6 +529,15 @@ impl VirtualDom {
/// Renders an `rsx` call into the Base Scope's allocator. /// Renders an `rsx` call into the Base Scope's allocator.
/// ///
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test. /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
///
/// ```rust
/// fn Base(cx: Context, props: &()) -> Element {
/// rsx!(cx, div {})
/// }
///
/// let dom = VirtualDom::new(Base);
/// let nodes = dom.render_nodes(rsx!("div"));
/// ```
pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
let mut machine = DiffState::new(&self.scopes); let mut machine = DiffState::new(&self.scopes);
machine.stack.push(DiffInstruction::Diff { new, old }); machine.stack.push(DiffInstruction::Diff { new, old });
@@ -498,6 +550,16 @@ impl VirtualDom {
/// Renders an `rsx` call into the Base Scope's allocator. /// Renders an `rsx` call into the Base Scope's allocator.
/// ///
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test. /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
///
///
/// ```rust
/// fn Base(cx: Context, props: &()) -> Element {
/// rsx!(cx, div {})
/// }
///
/// let dom = VirtualDom::new(Base);
/// let nodes = dom.render_nodes(rsx!("div"));
/// ```
pub fn create_vnodes<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> { pub fn create_vnodes<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
let nodes = self.render_vnodes(left); let nodes = self.render_vnodes(left);
let mut machine = DiffState::new(&self.scopes); let mut machine = DiffState::new(&self.scopes);
@@ -507,9 +569,19 @@ impl VirtualDom {
machine.mutations machine.mutations
} }
/// Renders an `rsx` call into the Base Scope's allocator. /// Renders an `rsx` call into the Base Scopes's arena.
/// ///
/// Useful when needing to render nodes from outside the VirtualDom, such as in a test. /// Useful when needing to diff two rsx! calls from outside the VirtualDom, such as in a test.
///
///
/// ```rust
/// fn Base(cx: Context, props: &()) -> Element {
/// rsx!(cx, div {})
/// }
///
/// let dom = VirtualDom::new(Base);
/// let nodes = dom.render_nodes(rsx!("div"));
/// ```
pub fn diff_lazynodes<'a>( pub fn diff_lazynodes<'a>(
&'a self, &'a self,
left: Option<LazyNodes<'a, '_>>, left: Option<LazyNodes<'a, '_>>,
@@ -666,14 +738,7 @@ impl<'a> Future for PollTasks<'a> {
// really this should just be retain_mut but that doesn't exist yet // really this should just be retain_mut but that doesn't exist yet
while let Some(mut task) = items.tasks.pop() { while let Some(mut task) = items.tasks.pop() {
// todo: does this make sense? if task.as_mut().poll(cx).is_ready() {
// I don't usually write futures by hand
// I think the futures neeed to be pinned using bumpbox or something
// right now, they're bump allocated so this shouldn't matter anyway - they're not going to move
let task_mut = task.as_mut();
let pinned = unsafe { Pin::new_unchecked(task_mut) };
if pinned.poll(cx).is_ready() {
all_pending = false all_pending = false
} else { } else {
unfinished_tasks.push(task); unfinished_tasks.push(task);
+1 -1
View File
@@ -69,7 +69,7 @@ fn events_generate() {
let mut dom = VirtualDom::new(App); let mut dom = VirtualDom::new(App);
let mut channel = dom.get_scheduler_channel(); let mut channel = dom.get_scheduler_channel();
assert!(dom.has_any_work()); assert!(dom.has_work());
let edits = dom.rebuild(); let edits = dom.rebuild();
assert_eq!( assert_eq!(
+3
View File
@@ -1,6 +1,9 @@
mod usestate; mod usestate;
pub use usestate::{use_state, AsyncUseState, UseState}; pub use usestate::{use_state, AsyncUseState, UseState};
mod usestate2;
// pub use usestate2::use_state2;
mod useref; mod useref;
pub use useref::*; pub use useref::*;
+9
View File
@@ -59,6 +59,15 @@ pub struct CoroutineHandle<'a> {
cx: Context<'a>, cx: Context<'a>,
inner: &'a State, inner: &'a State,
} }
impl Clone for CoroutineHandle<'_> {
fn clone(&self) -> Self {
CoroutineHandle {
cx: self.cx,
inner: self.inner,
}
}
}
impl Copy for CoroutineHandle<'_> {}
impl<'a> CoroutineHandle<'a> { impl<'a> CoroutineHandle<'a> {
pub fn start(&self) { pub fn start(&self) {
+27 -6
View File
@@ -54,18 +54,26 @@ pub fn use_state<'a, T: 'static>(
initial_state_fn: impl FnOnce() -> T, initial_state_fn: impl FnOnce() -> T,
) -> UseState<'a, T> { ) -> UseState<'a, T> {
cx.use_hook( cx.use_hook(
move |_| UseStateInner { move |_| {
current_val: initial_state_fn(), let first_val = initial_state_fn();
UseStateInner {
current_val: Rc::new(first_val),
update_callback: cx.schedule_update(), update_callback: cx.schedule_update(),
wip: Rc::new(RefCell::new(None)), wip: Rc::new(RefCell::new(None)),
update_scheuled: Cell::new(false), update_scheuled: Cell::new(false),
}
}, },
move |hook| { move |hook| {
hook.update_scheuled.set(false); hook.update_scheuled.set(false);
let mut new_val = hook.wip.borrow_mut(); let mut new_val = hook.wip.borrow_mut();
if new_val.is_some() { if new_val.is_some() {
hook.current_val = new_val.take().unwrap(); // if there's only one reference (weak or otherwise), we can just swap the values
if let Some(val) = Rc::get_mut(&mut hook.current_val) {
*val = new_val.take().unwrap();
} else {
hook.current_val = Rc::new(new_val.take().unwrap());
}
} }
UseState { inner: &*hook } UseState { inner: &*hook }
@@ -73,7 +81,7 @@ pub fn use_state<'a, T: 'static>(
) )
} }
struct UseStateInner<T: 'static> { struct UseStateInner<T: 'static> {
current_val: T, current_val: Rc<T>,
update_scheuled: Cell<bool>, update_scheuled: Cell<bool>,
update_callback: Rc<dyn Fn()>, update_callback: Rc<dyn Fn()>,
wip: Rc<RefCell<Option<T>>>, wip: Rc<RefCell<Option<T>>>,
@@ -115,6 +123,10 @@ impl<'a, T: 'static> UseState<'a, T> {
&self.inner.current_val &self.inner.current_val
} }
pub fn get_rc(&self) -> &'a Rc<T> {
&self.inner.current_val
}
/// Get the current status of the work-in-progress data /// Get the current status of the work-in-progress data
pub fn get_wip(&self) -> Ref<Option<T>> { pub fn get_wip(&self) -> Ref<Option<T>> {
self.inner.wip.borrow() self.inner.wip.borrow()
@@ -140,6 +152,7 @@ impl<'a, T: 'static> UseState<'a, T> {
AsyncUseState { AsyncUseState {
re_render: self.inner.update_callback.clone(), re_render: self.inner.update_callback.clone(),
wip: self.inner.wip.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 // "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
RefMut::map(self.inner.wip.borrow_mut(), |slot| { RefMut::map(self.inner.wip.borrow_mut(), |slot| {
if slot.is_none() { if slot.is_none() {
*slot = Some(self.inner.current_val.to_owned()); *slot = Some(self.inner.current_val.as_ref().to_owned());
} }
slot.as_mut().unwrap() slot.as_mut().unwrap()
}) })
} }
pub fn inner(self) -> T { 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 /// A less ergonmic but still capable form of use_state that's valid for `static lifetime
pub struct AsyncUseState<T: 'static> { pub struct AsyncUseState<T: 'static> {
inner: Rc<T>,
re_render: Rc<dyn Fn()>, re_render: Rc<dyn Fn()>,
wip: Rc<RefCell<Option<T>>>, wip: Rc<RefCell<Option<T>>>,
} }
@@ -278,4 +292,11 @@ impl<T> AsyncUseState<T> {
(self.re_render)(); (self.re_render)();
*self.wip.borrow_mut() = Some(val); *self.wip.borrow_mut() = Some(val);
} }
pub fn get(&self) -> &T {
self.inner.as_ref()
}
pub fn get_rc(&self) -> &Rc<T> {
&self.inner
}
} }
+227
View File
@@ -0,0 +1,227 @@
use dioxus_core::prelude::Context;
use std::{
borrow::{Borrow, BorrowMut},
cell::{Cell, Ref, RefCell, RefMut},
fmt::{Debug, Display},
ops::Not,
rc::Rc,
};
// /// Store state between component renders!
// ///
// /// ## Dioxus equivalent of UseStateInner2, designed for Rust
// ///
// /// The Dioxus version of `UseStateInner2` is the "king daddy" of state management. It allows you to ergonomically store and
// /// modify state between component renders. When the state is updated, the component will re-render.
// ///
// /// Dioxus' use_state basically wraps a RefCell with helper methods and integrates it with the VirtualDOM update system.
// ///
// /// [`use_state`] exposes a few helper methods to modify the underlying state:
// /// - `.set(new)` allows you to override the "work in progress" value with a new value
// /// - `.get_mut()` allows you to modify the WIP value
// /// - `.get_wip()` allows you to access the WIP value
// /// - `.deref()` provides the previous value (often done implicitly, though a manual dereference with `*` might be required)
// ///
// /// Additionally, a ton of std::ops traits are implemented for the `UseStateInner2` wrapper, meaning any mutative type operations
// /// will automatically be called on the WIP value.
// ///
// /// ## Combinators
// ///
// /// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality:
// /// - `.classic()` and `.split()` convert the hook into the classic React-style hook
// /// ```rust
// /// let (state, set_state) = use_state(cx, || 10).split()
// /// ```
// ///
// ///
// /// Usage:
// /// ```ignore
// /// const Example: FC<()> = |cx, props|{
// /// let counter = use_state(cx, || 0);
// /// let increment = |_| counter += 1;
// /// let decrement = |_| counter += 1;
// ///
// /// html! {
// /// <div>
// /// <h1>"Counter: {counter}" </h1>
// /// <button onclick={increment}> "Increment" </button>
// /// <button onclick={decrement}> "Decrement" </button>
// /// </div>
// /// }
// /// }
// /// ```
// pub fn use_state2<'a, T: 'static>(
// cx: Context<'a>,
// initial_state_fn: impl FnOnce() -> T,
// ) -> &'a UseState2<T> {
// cx.use_hook(
// move |_| {
// UseState2(Rc::new(UseStateInner2 {
// current_val: initial_state_fn(),
// update_callback: cx.schedule_update(),
// wip: None,
// update_scheuled: Cell::new(false),
// }))
// },
// move |hook: &mut UseState2<T>| {
// {
// let r = hook.0.as_ref();
// let mut state = r.borrow_mut();
// state.update_scheuled.set(false);
// if state.wip.is_some() {
// state.current_val = state.wip.take().unwrap();
// }
// }
// &*hook
// },
// )
// }
// pub struct UseState2<T: 'static>(Rc<UseStateInner2<T>>);
// impl<T> ToOwned for UseState2<T> {
// type Owned = UseState2<T>;
// fn to_owned(&self) -> Self::Owned {
// UseState2(self.0.clone())
// }
// }
// pub struct UseStateInner2<T: 'static> {
// current_val: T,
// update_scheuled: Cell<bool>,
// update_callback: Rc<dyn Fn()>,
// wip: Option<T>,
// }
// impl<T: Debug> Debug for UseStateInner2<T> {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// write!(f, "{:?}", self.current_val)
// }
// }
// impl<T> UseState2<T> {
// /// Tell the Dioxus Scheduler that we need to be processed
// pub fn needs_update(&self) {
// if !self.update_scheuled.get() {
// self.update_scheuled.set(true);
// (self.update_callback)();
// }
// }
// pub fn set(&mut self, new_val: T) -> Option<T> {
// self.needs_update();
// ip.wip.replace(new_val)
// }
// pub fn get(&self) -> &T {
// &self.current_val
// }
// }
// // impl<T: 'static + ToOwned<Owned = T>> UseState2<T> {
// // /// Gain mutable access to the new value. This method is only available when the value is a `ToOwned` type.
// // ///
// // /// Mutable access is derived by calling "ToOwned" (IE cloning) on the current value.
// // ///
// // /// To get a reference to the current value, use `.get()`
// // pub fn modify(&self) -> RefMut<T> {
// // // make sure we get processed
// // self.0.needs_update();
// // // Bring out the new value, cloning if it we need to
// // // "get_mut" is locked behind "ToOwned" to make it explicit that cloning occurs to use this
// // RefMut::map(self.wip.borrow_mut(), |slot| {
// // if slot.is_none() {
// // *slot = Some(self.current_val.to_owned());
// // }
// // slot.as_mut().unwrap()
// // })
// // }
// // pub fn inner(self) -> T {
// // self.current_val.to_owned()
// // }
// // }
// impl<T> std::ops::Deref for UseStateInner2<T> {
// type Target = T;
// fn deref(&self) -> &Self::Target {
// self.get()
// }
// }
// use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Sub, SubAssign};
// use crate::UseState;
// impl<T: Copy + Add<T, Output = T>> Add<T> for UseStateInner2<T> {
// type Output = T;
// fn add(self, rhs: T) -> Self::Output {
// self.current_val.add(rhs)
// }
// }
// impl<T: Copy + Add<T, Output = T>> AddAssign<T> for UseStateInner2<T> {
// fn add_assign(&mut self, rhs: T) {
// self.set(self.current_val.add(rhs));
// }
// }
// impl<T: Copy + Sub<T, Output = T>> Sub<T> for UseStateInner2<T> {
// type Output = T;
// fn sub(self, rhs: T) -> Self::Output {
// self.current_val.sub(rhs)
// }
// }
// impl<T: Copy + Sub<T, Output = T>> SubAssign<T> for UseStateInner2<T> {
// fn sub_assign(&mut self, rhs: T) {
// self.set(self.current_val.sub(rhs));
// }
// }
// /// MUL
// impl<T: Copy + Mul<T, Output = T>> Mul<T> for UseStateInner2<T> {
// type Output = T;
// fn mul(self, rhs: T) -> Self::Output {
// self.current_val.mul(rhs)
// }
// }
// impl<T: Copy + Mul<T, Output = T>> MulAssign<T> for UseStateInner2<T> {
// fn mul_assign(&mut self, rhs: T) {
// self.set(self.current_val.mul(rhs));
// }
// }
// /// DIV
// impl<T: Copy + Div<T, Output = T>> Div<T> for UseStateInner2<T> {
// type Output = T;
// fn div(self, rhs: T) -> Self::Output {
// self.current_val.div(rhs)
// }
// }
// impl<T: Copy + Div<T, Output = T>> DivAssign<T> for UseStateInner2<T> {
// fn div_assign(&mut self, rhs: T) {
// self.set(self.current_val.div(rhs));
// }
// }
// impl<V, T: PartialEq<V>> PartialEq<V> for UseStateInner2<T> {
// fn eq(&self, other: &V) -> bool {
// self.get() == other
// }
// }
// impl<O, T: Not<Output = O> + Copy> Not for UseStateInner2<T> {
// type Output = O;
// fn not(self) -> Self::Output {
// !*self.get()
// }
// }
// // enable displaty for the handle
// impl<T: 'static + Display> std::fmt::Display for UseStateInner2<T> {
// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
// write!(f, "{}", self.current_val)
// }
// }
+25
View File
@@ -22,3 +22,28 @@ async fn main() -> tide::Result<()> {
Dioxus LiveView runs your Dioxus apps on the server 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" }
))
}
```
+1 -14
View File
@@ -1,12 +1,4 @@
/// Wasm-bindgen has a performance option to intern commonly used phrases pub static BUILTIN_INTERNED_STRINGS: &[&'static str] = &[
/// This saves the decoding cost, making the interaction of Rust<->JS more performant.
/// 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 // Important tags to dioxus
"dioxus-id", "dioxus-id",
"dioxus", "dioxus",
@@ -304,8 +296,3 @@ pub(crate) fn intern_cached_strings() {
"13", "13",
"14", "14",
]; ];
for s in cached_words {
wasm_bindgen::intern(s);
}
}
+10
View File
@@ -11,6 +11,7 @@
pub struct WebConfig { pub struct WebConfig {
pub(crate) hydrate: bool, pub(crate) hydrate: bool,
pub(crate) rootname: String, pub(crate) rootname: String,
pub(crate) cached_strings: Vec<String>,
} }
impl Default for WebConfig { impl Default for WebConfig {
@@ -18,6 +19,7 @@ impl Default for WebConfig {
Self { Self {
hydrate: false, hydrate: false,
rootname: "main".to_string(), rootname: "main".to_string(),
cached_strings: Vec::new(),
} }
} }
} }
@@ -41,4 +43,12 @@ impl WebConfig {
self.rootname = name.into(); self.rootname = name.into();
self self
} }
/// Set the name of the element that Dioxus will use as the root.
///
/// This is akint to calling React.render() on the element with the specified name.
pub fn with_string_cache(mut self, cache: Vec<String>) -> Self {
self.cached_strings = cache;
self
}
} }
+14 -14
View File
@@ -56,7 +56,6 @@ use std::rc::Rc;
pub use crate::cfg::WebConfig; pub use crate::cfg::WebConfig;
use crate::dom::load_document; use crate::dom::load_document;
use cache::intern_cached_strings;
use dioxus::SchedulerMsg; use dioxus::SchedulerMsg;
use dioxus::VirtualDom; use dioxus::VirtualDom;
pub use dioxus_core as dioxus; pub use dioxus_core as dioxus;
@@ -82,15 +81,15 @@ mod ric_raf;
/// ///
/// ```rust /// ```rust
/// fn main() { /// fn main() {
/// dioxus_web::launch(App, |c| c); /// dioxus_web::launch(App);
/// } /// }
/// ///
/// static App: FC<()> = |cx, props| { /// static App: Component<()> = |cx, props| {
/// rsx!(cx, div {"hello world"}) /// rsx!(cx, div {"hello world"})
/// } /// }
/// ``` /// ```
pub fn launch(root_component: Component<()>, configuration: impl FnOnce(WebConfig) -> WebConfig) { pub fn launch(root_component: Component<()>) {
launch_with_props(root_component, (), configuration) launch_with_props(root_component, (), |c| c);
} }
/// Launches the VirtualDOM from the specified component function and props. /// Launches the VirtualDOM from the specified component function and props.
@@ -109,7 +108,7 @@ pub fn launch(root_component: Component<()>, configuration: impl FnOnce(WebConfi
/// name: String /// name: String
/// } /// }
/// ///
/// static App: FC<RootProps> = |cx, props| { /// static App: Component<RootProps> = |cx, props| {
/// rsx!(cx, div {"hello {props.name}"}) /// rsx!(cx, div {"hello {props.name}"})
/// } /// }
/// ``` /// ```
@@ -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) { pub async fn run_with_props<T: 'static + Send>(root: Component<T>, root_props: T, cfg: WebConfig) {
let mut dom = VirtualDom::new_with_props(root, root_props); let mut dom = VirtualDom::new_with_props(root, root_props);
intern_cached_strings(); for s in crate::cache::BUILTIN_INTERNED_STRINGS {
wasm_bindgen::intern(s);
}
for s in &cfg.cached_strings {
wasm_bindgen::intern(s);
}
let should_hydrate = cfg.hydrate; let should_hydrate = cfg.hydrate;
@@ -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 // hydrating is simply running the dom for a single render. If the page is already written, then the corresponding
// ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory // ElementIds should already line up because the web_sys dom has already loaded elements with the DioxusID into memory
if !should_hydrate { if !should_hydrate {
// log::info!("Applying rebuild edits..., {:?}", mutations);
websys_dom.process_edits(&mut mutations.edits); websys_dom.process_edits(&mut mutations.edits);
} }
@@ -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. // if there is work then this future resolves immediately.
dom.wait_for_work().await; dom.wait_for_work().await;
// // wait for the mainthread to schedule us in // wait for the mainthread to schedule us in
// let mut deadline = work_loop.wait_for_idle_time().await; let mut deadline = work_loop.wait_for_idle_time().await;
// run the virtualdom work phase until the frame deadline is reached // run the virtualdom work phase until the frame deadline is reached
let mutations = dom.work_with_deadline(|| false); let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
// // run the virtualdom work phase until the frame deadline is reached
// let mutations = dom.work_with_deadline(|| (&mut deadline).now_or_never().is_some());
// wait for the animation frame to fire so we can apply our changes // wait for the animation frame to fire so we can apply our changes
work_loop.wait_for_raf().await; work_loop.wait_for_raf().await;
for mut edit in mutations { for mut edit in mutations {
// actually apply our changes during the animation frame // actually apply our changes during the animation frame
// log::info!("Applying change edits..., {:?}", edit);
websys_dom.process_edits(&mut edit.edits); websys_dom.process_edits(&mut edit.edits);
} }
} }