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
+760 -434

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
/// 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> {
+2 -13
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,
};
-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(),
}
}
+49 -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.
@@ -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();
-7
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;
+84 -19
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();
@@ -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);