chore: more refactoring, docs
This commit is contained in:
1 parent
f7df6a9893
commit
c44bd11fe5
27 files changed
+657
-382
No files matched your search
@@ -1,7 +1,5 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
use crate::{
|
||||
factory::{ComponentReturn, RenderReturn},
|
||||
innerlude::Scoped,
|
||||
@@ -25,17 +23,6 @@ where
|
||||
pub _marker: PhantomData<A>,
|
||||
}
|
||||
|
||||
impl<'a> VComponentProps<'a, (), ()> {
|
||||
pub fn new_empty(render_fn: fn(Scope) -> Element) -> Self {
|
||||
Self {
|
||||
render_fn,
|
||||
memo: <() as PartialEq>::eq,
|
||||
props: (),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, P, A, F: ComponentReturn<'a, A>> VComponentProps<'a, P, A, F> {
|
||||
pub(crate) fn new(
|
||||
render_fn: fn(Scope<'a, P>) -> F,
|
||||
|
||||
@@ -8,6 +8,15 @@ pub struct ElementPath {
|
||||
pub element: usize,
|
||||
}
|
||||
|
||||
impl ElementPath {
|
||||
pub fn null() -> Self {
|
||||
Self {
|
||||
template: std::ptr::null_mut(),
|
||||
element: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
pub fn next_element(&mut self, template: &VNode) -> ElementId {
|
||||
let entry = self.elements.vacant_entry();
|
||||
|
||||
@@ -21,4 +21,8 @@ impl BumpFrame {
|
||||
self.bump.reset();
|
||||
self.node.set(std::ptr::null_mut());
|
||||
}
|
||||
|
||||
pub unsafe fn load_node<'b>(&self) -> &'b RenderReturn<'b> {
|
||||
unsafe { std::mem::transmute(&*self.node.get()) }
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
// pub trait IntoComponentType<T> {
|
||||
// fn into_component_type(self) -> ComponentType;
|
||||
// }
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use std::future::Future;
|
||||
|
||||
use crate::{scopes::Scope, Element};
|
||||
|
||||
pub type Component<'a, T = ()> = fn(Scope<'a, T>) -> Element<'a>;
|
||||
@@ -130,7 +130,10 @@ impl VirtualDom {
|
||||
) {
|
||||
match *node {
|
||||
// Todo: create the children's template
|
||||
TemplateNode::Dynamic(_) => mutations.push(CreatePlaceholder { id: ElementId(0) }),
|
||||
TemplateNode::Dynamic(_) => {
|
||||
let id = self.next_element(template);
|
||||
mutations.push(CreatePlaceholder { id })
|
||||
}
|
||||
TemplateNode::Text(value) => mutations.push(CreateText { value }),
|
||||
TemplateNode::DynamicText { .. } => mutations.push(CreateText {
|
||||
value: "placeholder",
|
||||
@@ -220,7 +223,7 @@ impl VirtualDom {
|
||||
|
||||
let split_off = unsafe { std::mem::transmute(split_off) };
|
||||
|
||||
boundary_mut.mutations.mutations = split_off;
|
||||
boundary_mut.mutations.edits = split_off;
|
||||
boundary_mut
|
||||
.waiting_on
|
||||
.extend(self.collected_leaves.drain(..));
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use std::any::Any;
|
||||
|
||||
use crate::factory::RenderReturn;
|
||||
use crate::innerlude::Mutations;
|
||||
use crate::virtual_dom::VirtualDom;
|
||||
use crate::{Attribute, AttributeValue, TemplateNode};
|
||||
|
||||
use crate::any_props::VComponentProps;
|
||||
|
||||
use crate::component::Component;
|
||||
use crate::mutations::Mutation;
|
||||
use crate::nodes::{DynamicNode, Template, TemplateId};
|
||||
use crate::scopes::Scope;
|
||||
@@ -41,6 +41,62 @@ impl Ord for DirtyScope {
|
||||
impl<'b> VirtualDom {
|
||||
pub fn diff_scope(&mut self, mutations: &mut Mutations<'b>, scope: ScopeId) {
|
||||
let scope_state = &mut self.scopes[scope.0];
|
||||
|
||||
let cur_arena = scope_state.current_frame();
|
||||
let prev_arena = scope_state.previous_frame();
|
||||
|
||||
// relax the borrow checker
|
||||
let cur_arena: &BumpFrame = unsafe { std::mem::transmute(cur_arena) };
|
||||
let prev_arena: &BumpFrame = unsafe { std::mem::transmute(prev_arena) };
|
||||
|
||||
// Make sure the nodes arent null (they've been set properly)
|
||||
assert_ne!(
|
||||
cur_arena.node.get(),
|
||||
std::ptr::null_mut(),
|
||||
"Call rebuild before diffing"
|
||||
);
|
||||
assert_ne!(
|
||||
prev_arena.node.get(),
|
||||
std::ptr::null_mut(),
|
||||
"Call rebuild before diffing"
|
||||
);
|
||||
|
||||
self.scope_stack.push(scope);
|
||||
let left = unsafe { prev_arena.load_node() };
|
||||
let right = unsafe { cur_arena.load_node() };
|
||||
self.diff_maybe_node(mutations, left, right);
|
||||
self.scope_stack.pop();
|
||||
}
|
||||
|
||||
fn diff_maybe_node(
|
||||
&mut self,
|
||||
m: &mut Mutations<'b>,
|
||||
left: &'b RenderReturn<'b>,
|
||||
right: &'b RenderReturn<'b>,
|
||||
) {
|
||||
use RenderReturn::{Async, Sync};
|
||||
match (left, right) {
|
||||
// diff
|
||||
(Sync(Some(l)), Sync(Some(r))) => self.diff_node(m, l, r),
|
||||
|
||||
// remove old with placeholder
|
||||
(Sync(Some(l)), Sync(None)) | (Sync(Some(l)), Async(_)) => {
|
||||
//
|
||||
let id = self.next_element(l); // todo!
|
||||
m.push(Mutation::CreatePlaceholder { id });
|
||||
self.drop_template(m, l, true);
|
||||
}
|
||||
|
||||
// remove placeholder with nodes
|
||||
(Sync(None), Sync(Some(_))) => {}
|
||||
(Async(_), Sync(Some(v))) => {}
|
||||
|
||||
// nothing...
|
||||
(Async(_), Async(_))
|
||||
| (Sync(None), Sync(None))
|
||||
| (Sync(None), Async(_))
|
||||
| (Async(_), Sync(None)) => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn diff_node(
|
||||
|
||||
@@ -98,53 +98,9 @@ pub enum EventPriority {
|
||||
Low = 0,
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
/// Returns None if no element could be found
|
||||
pub fn handle_event<T: 'static>(&mut self, event: &UiEvent<T>) -> Option<()> {
|
||||
let path = self.elements.get(event.element.0)?;
|
||||
|
||||
let location = unsafe { &mut *path.template }
|
||||
.dynamic_attrs
|
||||
.iter()
|
||||
.position(|attr| attr.mounted_element.get() == event.element)?;
|
||||
|
||||
let mut index = Some((path.template, location));
|
||||
|
||||
let mut listeners = Vec::<&Attribute>::new();
|
||||
|
||||
while let Some((raw_parent, dyn_index)) = index {
|
||||
let parent = unsafe { &mut *raw_parent };
|
||||
let path = parent.template.node_paths[dyn_index];
|
||||
|
||||
listeners.extend(
|
||||
parent
|
||||
.dynamic_attrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(idx, attr)| {
|
||||
match is_path_ascendant(parent.template.node_paths[idx], path) {
|
||||
true if attr.name == event.name => Some(attr),
|
||||
_ => None,
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
index = parent.parent;
|
||||
}
|
||||
|
||||
for listener in listeners {
|
||||
if let AttributeValue::Listener(listener) = &listener.value {
|
||||
(listener.borrow_mut())(&event.event)
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
}
|
||||
|
||||
// ensures a strict descendant relationship
|
||||
// returns false if the paths are equal
|
||||
fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
|
||||
pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
|
||||
if small.len() >= big.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::{cell::Cell, fmt::Arguments, pin::Pin};
|
||||
use std::{cell::Cell, fmt::Arguments};
|
||||
|
||||
use bumpalo::boxed::Box as BumpBox;
|
||||
use bumpalo::Bump;
|
||||
@@ -123,13 +123,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn takes_it() {
|
||||
fn demo(cx: Scope) -> Element {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RenderReturn<'a> {
|
||||
Sync(Element<'a>),
|
||||
Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
|
||||
@@ -144,8 +137,6 @@ impl<'a> RenderReturn<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub type FiberLeaf<'a> = Pin<BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>>;
|
||||
|
||||
pub trait IntoVnode<'a, A = ()> {
|
||||
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a>;
|
||||
}
|
||||
@@ -162,6 +153,12 @@ impl<'a, 'b> IntoVnode<'a> for VNode<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoVnode<'a> for Option<VNode<'a>> {
|
||||
fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
|
||||
self.expect("please allow optional nodes in")
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoVnode<'a> for &'a VNode<'a> {
|
||||
fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
|
||||
VNode {
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
use crate::{nodes::VNode, scopes::ScopeId, virtual_dom::VirtualDom, DynamicNode};
|
||||
use crate::{nodes::VNode, scopes::ScopeId, virtual_dom::VirtualDom, DynamicNode, Mutations};
|
||||
|
||||
impl VirtualDom {
|
||||
impl<'b> VirtualDom {
|
||||
pub fn drop_scope(&mut self, id: ScopeId) {
|
||||
let scope = self.scopes.get(id.0).unwrap();
|
||||
// let scope = self.scopes.get(id.0).unwrap();
|
||||
|
||||
let root = scope.root_node();
|
||||
let root = unsafe { std::mem::transmute(root) };
|
||||
// let root = scope.root_node();
|
||||
// let root = unsafe { std::mem::transmute(root) };
|
||||
|
||||
self.drop_template(root);
|
||||
// self.drop_template(root, false);
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn drop_template<'a>(&'a mut self, template: &'a VNode<'a>) {
|
||||
pub fn drop_template(
|
||||
&mut self,
|
||||
mutations: &mut Mutations,
|
||||
template: &'b VNode<'b>,
|
||||
gen_roots: bool,
|
||||
) {
|
||||
for node in template.dynamic_nodes.iter() {
|
||||
match node {
|
||||
DynamicNode::Text { id, .. } => {}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
mod any_props;
|
||||
mod arena;
|
||||
mod bump_frame;
|
||||
mod component;
|
||||
mod create;
|
||||
mod diff;
|
||||
mod events;
|
||||
@@ -77,7 +76,10 @@ pub use crate::innerlude::{
|
||||
ElementId,
|
||||
ElementPath,
|
||||
EventPriority,
|
||||
Fragment,
|
||||
LazyNodes,
|
||||
Mutation,
|
||||
Mutations,
|
||||
NodeFactory,
|
||||
Properties,
|
||||
Scope,
|
||||
@@ -100,9 +102,9 @@ pub use crate::innerlude::{
|
||||
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||
pub mod prelude {
|
||||
pub use crate::innerlude::{
|
||||
fc_to_builder, Attribute, DynamicNode, Element, EventPriority, LazyNodes, NodeFactory,
|
||||
Properties, Scope, ScopeId, ScopeState, Scoped, SuspenseBoundary, SuspenseContext, TaskId,
|
||||
Template, TemplateAttribute, TemplateNode, UiEvent, VNode, VirtualDom,
|
||||
fc_to_builder, Element, EventPriority, Fragment, LazyNodes, NodeFactory, Properties, Scope,
|
||||
ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent,
|
||||
VNode, VirtualDom,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -135,24 +137,3 @@ macro_rules! to_owned {
|
||||
let mut $es = $es.to_owned();
|
||||
)*}
|
||||
}
|
||||
|
||||
/// A helper macro for values into callbacks for async environements.
|
||||
///
|
||||
///
|
||||
macro_rules! callback {
|
||||
() => {};
|
||||
}
|
||||
|
||||
/// Convert a hook into a hook with an implicit dependency list by analyzing the closure.
|
||||
///
|
||||
/// ```
|
||||
/// // Convert hooks with annoying dependencies into...
|
||||
///
|
||||
/// let val = use_effect(cx, (val,) |(val,)| println!("thing {val}"))
|
||||
///
|
||||
/// // a simple closure
|
||||
/// let val = use_effect!(cx, |val| async { println!("thing {val}")) });
|
||||
/// ```
|
||||
macro_rules! make_dep_fn {
|
||||
() => {};
|
||||
}
|
||||
@@ -4,14 +4,14 @@ use crate::arena::ElementId;
|
||||
pub struct Mutations<'a> {
|
||||
pub subtree: usize,
|
||||
pub template_mutations: Vec<Mutation<'a>>,
|
||||
pub mutations: Vec<Mutation<'a>>,
|
||||
pub edits: Vec<Mutation<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Mutations<'a> {
|
||||
pub fn new(subtree: usize) -> Self {
|
||||
Self {
|
||||
subtree,
|
||||
mutations: Vec::new(),
|
||||
edits: Vec::new(),
|
||||
template_mutations: Vec::new(),
|
||||
}
|
||||
}
|
||||
@@ -21,13 +21,13 @@ impl<'a> std::ops::Deref for Mutations<'a> {
|
||||
type Target = Vec<Mutation<'a>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.mutations
|
||||
&self.edits
|
||||
}
|
||||
}
|
||||
|
||||
impl std::ops::DerefMut for Mutations<'_> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.mutations
|
||||
&mut self.edits
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ impl std::ops::DerefMut for Mutations<'_> {
|
||||
each subtree has its own numbering scheme
|
||||
*/
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Mutation<'a> {
|
||||
SetAttribute {
|
||||
name: &'a str,
|
||||
|
||||
@@ -64,36 +64,37 @@ impl<'a> Properties for FragmentProps<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
// /// Create inline fragments using Component syntax.
|
||||
// ///
|
||||
// /// ## Details
|
||||
// ///
|
||||
// /// Fragments capture a series of children without rendering extra nodes.
|
||||
// ///
|
||||
// /// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
|
||||
// /// a key is needed to identify each item.
|
||||
// ///
|
||||
// /// ## Example
|
||||
// ///
|
||||
// /// ```rust, ignore
|
||||
// /// rsx!{
|
||||
// /// Fragment { key: "abc" }
|
||||
// /// }
|
||||
// /// ```
|
||||
// ///
|
||||
// /// ## Usage
|
||||
// ///
|
||||
// /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||
// /// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
|
||||
// ///
|
||||
// /// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
||||
// ///
|
||||
// /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||
// #[allow(non_upper_case_globals, non_snake_case)]
|
||||
// pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
||||
// let i = cx.props.0.as_ref().map(|f| f.decouple());
|
||||
// cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
|
||||
// }
|
||||
/// Create inline fragments using Component syntax.
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// Fragments capture a series of children without rendering extra nodes.
|
||||
///
|
||||
/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
|
||||
/// a key is needed to identify each item.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// rsx!{
|
||||
/// Fragment { key: "abc" }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Usage
|
||||
///
|
||||
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||
/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
|
||||
///
|
||||
/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
|
||||
///
|
||||
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||
#[allow(non_upper_case_globals, non_snake_case)]
|
||||
pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
||||
// let i = cx.props.0.as_ref().map(|f| f.decouple());
|
||||
// cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
|
||||
todo!("Fragment")
|
||||
}
|
||||
|
||||
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
|
||||
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
|
||||
|
||||
@@ -14,16 +14,10 @@ pub use waker::RcWake;
|
||||
///
|
||||
/// These messages control how the scheduler will process updates to the UI.
|
||||
#[derive(Debug)]
|
||||
pub enum SchedulerMsg {
|
||||
/// Events from the Renderer
|
||||
Event,
|
||||
|
||||
pub(crate) enum SchedulerMsg {
|
||||
/// Immediate updates from Components that mark them as dirty
|
||||
Immediate(ScopeId),
|
||||
|
||||
/// Mark all components as dirty and update them
|
||||
DirtyAll,
|
||||
|
||||
/// A task has woken and needs to be progressed
|
||||
TaskNotified(TaskId),
|
||||
|
||||
|
||||
@@ -29,12 +29,11 @@ impl SuspenseBoundary {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SuspenseLeaf {
|
||||
pub(crate) struct SuspenseLeaf {
|
||||
pub id: SuspenseId,
|
||||
pub scope_id: ScopeId,
|
||||
pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||
pub notified: Cell<bool>,
|
||||
|
||||
pub task: *mut dyn Future<Output = Element<'static>>,
|
||||
}
|
||||
|
||||
|
||||
@@ -57,11 +57,6 @@ impl Scheduler {
|
||||
pub fn remove(&self, id: TaskId) {
|
||||
//
|
||||
}
|
||||
|
||||
// Aborts the future
|
||||
pub fn abort(&self, id: TaskId) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
impl RcWake for LocalTask {
|
||||
|
||||
@@ -1,110 +1,92 @@
|
||||
use futures_util::{FutureExt, StreamExt};
|
||||
use futures_util::FutureExt;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use crate::{
|
||||
diff::DirtyScope,
|
||||
factory::RenderReturn,
|
||||
innerlude::{Mutation, Mutations, SuspenseContext},
|
||||
VNode, VirtualDom,
|
||||
ScopeId, TaskId, VNode, VirtualDom,
|
||||
};
|
||||
|
||||
use super::{waker::RcWake, SchedulerMsg, SuspenseLeaf};
|
||||
use super::{waker::RcWake, SuspenseId, SuspenseLeaf};
|
||||
|
||||
impl VirtualDom {
|
||||
/// Wait for futures internal to the virtualdom
|
||||
/// Handle notifications by tasks inside the scheduler
|
||||
///
|
||||
/// This is cancel safe, so if the future is dropped, you can push events into the virtualdom
|
||||
pub async fn wait_for_work(&mut self) {
|
||||
// todo: make sure the scheduler queue is completely drained
|
||||
loop {
|
||||
match self.rx.next().await.unwrap() {
|
||||
SchedulerMsg::Event => break,
|
||||
/// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
|
||||
/// queue
|
||||
pub fn handle_task_wakeup(&mut self, id: TaskId) {
|
||||
let mut tasks = self.scheduler.tasks.borrow_mut();
|
||||
let task = &tasks[id.0];
|
||||
|
||||
SchedulerMsg::Immediate(id) => {
|
||||
let height = self.scopes[id.0].height;
|
||||
self.dirty_scopes.insert(DirtyScope { height, id });
|
||||
break;
|
||||
}
|
||||
// If the task completes...
|
||||
if task.progress() {
|
||||
// Remove it from the scope so we dont try to double drop it when the scope dropes
|
||||
self.scopes[task.scope.0].spawned_tasks.remove(&id);
|
||||
|
||||
SchedulerMsg::DirtyAll => todo!(),
|
||||
// Remove it from the scheduler
|
||||
tasks.remove(id.0);
|
||||
}
|
||||
}
|
||||
|
||||
SchedulerMsg::TaskNotified(id) => {
|
||||
let mut tasks = self.scheduler.tasks.borrow_mut();
|
||||
let task = &tasks[id.0];
|
||||
pub fn handle_suspense_wakeup(&mut self, id: SuspenseId) {
|
||||
println!("suspense notified");
|
||||
|
||||
// If the task completes...
|
||||
if task.progress() {
|
||||
// Remove it from the scope so we dont try to double drop it when the scope dropes
|
||||
self.scopes[task.scope.0].spawned_tasks.remove(&id);
|
||||
let leaf = self
|
||||
.scheduler
|
||||
.leaves
|
||||
.borrow_mut()
|
||||
.get(id.0)
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
// Remove it from the scheduler
|
||||
tasks.remove(id.0);
|
||||
}
|
||||
}
|
||||
let scope_id = leaf.scope_id;
|
||||
|
||||
SchedulerMsg::SuspenseNotified(id) => {
|
||||
println!("suspense notified");
|
||||
// todo: cache the waker
|
||||
let waker = leaf.waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
||||
let leaf = self
|
||||
.scheduler
|
||||
.leaves
|
||||
.borrow_mut()
|
||||
.get(id.0)
|
||||
.unwrap()
|
||||
.clone();
|
||||
// Safety: the future is always pinned to the bump arena
|
||||
let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) };
|
||||
let as_pinned_mut = &mut pinned;
|
||||
|
||||
let scope_id = leaf.scope_id;
|
||||
// the component finished rendering and gave us nodes
|
||||
// we should attach them to that component and then render its children
|
||||
// continue rendering the tree until we hit yet another suspended component
|
||||
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
|
||||
let boundary = &self.scopes[leaf.scope_id.0]
|
||||
.consume_context::<SuspenseContext>()
|
||||
.unwrap();
|
||||
|
||||
// todo: cache the waker
|
||||
let waker = leaf.waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
println!("ready pool");
|
||||
|
||||
// Safety: the future is always pinned to the bump arena
|
||||
let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) };
|
||||
let as_pinned_mut = &mut pinned;
|
||||
let mut fiber = boundary.borrow_mut();
|
||||
|
||||
// the component finished rendering and gave us nodes
|
||||
// we should attach them to that component and then render its children
|
||||
// continue rendering the tree until we hit yet another suspended component
|
||||
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
|
||||
let boundary = &self.scopes[leaf.scope_id.0]
|
||||
.consume_context::<SuspenseContext>()
|
||||
.unwrap();
|
||||
println!(
|
||||
"Existing mutations {:?}, scope {:?}",
|
||||
fiber.mutations, fiber.id
|
||||
);
|
||||
|
||||
println!("ready pool");
|
||||
let scope = &mut self.scopes[scope_id.0];
|
||||
let arena = scope.current_frame();
|
||||
|
||||
let mut fiber = boundary.borrow_mut();
|
||||
let ret = arena.bump.alloc(RenderReturn::Sync(new_nodes));
|
||||
arena.node.set(ret);
|
||||
|
||||
println!(
|
||||
"Existing mutations {:?}, scope {:?}",
|
||||
fiber.mutations, fiber.id
|
||||
);
|
||||
if let RenderReturn::Sync(Some(template)) = ret {
|
||||
let mutations = &mut fiber.mutations;
|
||||
let template: &VNode = unsafe { std::mem::transmute(template) };
|
||||
let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) };
|
||||
|
||||
let scope = &mut self.scopes[scope_id.0];
|
||||
let arena = scope.current_arena();
|
||||
self.scope_stack.push(scope_id);
|
||||
self.create(mutations, template);
|
||||
self.scope_stack.pop();
|
||||
|
||||
let ret = arena.bump.alloc(RenderReturn::Sync(new_nodes));
|
||||
arena.node.set(ret);
|
||||
|
||||
if let RenderReturn::Sync(Some(template)) = ret {
|
||||
let mutations = &mut fiber.mutations;
|
||||
let template: &VNode = unsafe { std::mem::transmute(template) };
|
||||
let mutations: &mut Mutations =
|
||||
unsafe { std::mem::transmute(mutations) };
|
||||
|
||||
self.scope_stack.push(scope_id);
|
||||
self.create(mutations, template);
|
||||
self.scope_stack.pop();
|
||||
|
||||
println!("{:#?}", mutations);
|
||||
} else {
|
||||
println!("nodes arent right");
|
||||
}
|
||||
} else {
|
||||
println!("not ready");
|
||||
}
|
||||
}
|
||||
println!("{:#?}", mutations);
|
||||
} else {
|
||||
println!("nodes arent right");
|
||||
}
|
||||
} else {
|
||||
println!("not ready");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@ use std::{
|
||||
};
|
||||
|
||||
use bumpalo::Bump;
|
||||
use futures_channel::mpsc::UnboundedSender;
|
||||
use std::future::Future;
|
||||
|
||||
use crate::{
|
||||
@@ -69,20 +68,27 @@ pub struct ScopeState {
|
||||
}
|
||||
|
||||
impl ScopeState {
|
||||
pub fn current_arena(&self) -> &BumpFrame {
|
||||
pub fn current_frame(&self) -> &BumpFrame {
|
||||
match self.render_cnt % 2 {
|
||||
0 => &self.node_arena_1,
|
||||
1 => &self.node_arena_2,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
pub fn previous_frame(&self) -> &BumpFrame {
|
||||
match self.render_cnt % 2 {
|
||||
1 => &self.node_arena_1,
|
||||
0 => &self.node_arena_2,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bump(&self) -> &Bump {
|
||||
&self.current_arena().bump
|
||||
&self.current_frame().bump
|
||||
}
|
||||
|
||||
pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
|
||||
let r = unsafe { &*self.current_arena().node.get() };
|
||||
let r = unsafe { &*self.current_frame().node.get() };
|
||||
unsafe { std::mem::transmute(r) }
|
||||
}
|
||||
|
||||
@@ -142,11 +148,6 @@ impl ScopeState {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Get a handle to the raw update scheduler channel
|
||||
pub fn scheduler_channel(&self) -> UnboundedSender<SchedulerMsg> {
|
||||
self.tasks.sender.clone()
|
||||
}
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
///
|
||||
/// ## Notice: you should prefer using [`schedule_update_any`] and [`scope_id`]
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
use crate::any_props::VComponentProps;
|
||||
use crate::arena::ElementPath;
|
||||
use crate::diff::DirtyScope;
|
||||
use crate::factory::RenderReturn;
|
||||
use crate::innerlude::{Mutations, Scheduler, SchedulerMsg};
|
||||
use crate::mutations::Mutation;
|
||||
use crate::nodes::{Template, TemplateId};
|
||||
use crate::{
|
||||
any_props::VComponentProps,
|
||||
arena::ElementId,
|
||||
arena::ElementPath,
|
||||
diff::DirtyScope,
|
||||
factory::RenderReturn,
|
||||
innerlude::{is_path_ascendant, Mutations, Scheduler, SchedulerMsg},
|
||||
mutations::Mutation,
|
||||
nodes::{Template, TemplateId},
|
||||
scheduler::{SuspenseBoundary, SuspenseId},
|
||||
scopes::{ScopeId, ScopeState},
|
||||
Attribute, AttributeValue, Element, Scope, SuspenseContext, UiEvent,
|
||||
};
|
||||
use crate::{scheduler, Element, Scope};
|
||||
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use scheduler::{SuspenseBoundary, SuspenseId};
|
||||
use futures_util::{pin_mut, FutureExt, StreamExt};
|
||||
use slab::Slab;
|
||||
use std::collections::{BTreeSet, HashMap};
|
||||
use std::future::Future;
|
||||
@@ -56,56 +56,88 @@ use std::rc::Rc;
|
||||
/// let edits = vdom.rebuild();
|
||||
/// ```
|
||||
///
|
||||
/// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler.
|
||||
/// To call listeners inside the VirtualDom, call [`VirtualDom::handle_event`] with the appropriate event data.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let channel = vdom.get_scheduler_channel();
|
||||
/// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent {
|
||||
/// // ...
|
||||
/// }))
|
||||
/// vdom.handle_event(event);
|
||||
/// ```
|
||||
///
|
||||
/// While waiting for UserEvents to occur, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
|
||||
/// While no events are ready, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// vdom.wait_for_work().await;
|
||||
/// ```
|
||||
///
|
||||
/// Once work is ready, call [`VirtualDom::work_with_deadline`] to compute the differences between the previous and
|
||||
/// Once work is ready, call [`VirtualDom::render_with_deadline`] to compute the differences between the previous and
|
||||
/// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
|
||||
/// handled by the renderer.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// let mutations = vdom.work_with_deadline(|| false);
|
||||
/// for edit in mutations {
|
||||
/// apply(edit);
|
||||
/// let mutations = vdom.work_with_deadline(tokio::time::sleep(Duration::from_millis(100)));
|
||||
///
|
||||
/// for edit in mutations.edits {
|
||||
/// real_dom.apply(edit);
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// To not wait for suspense while diffing the VirtualDom, call [`VirtualDom::render_immediate`] or pass an immediately
|
||||
/// ready future to [`VirtualDom::render_with_deadline`].
|
||||
///
|
||||
///
|
||||
/// ## Building an event loop around Dioxus:
|
||||
///
|
||||
/// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// fn App(cx: Scope) -> Element {
|
||||
/// ```rust
|
||||
/// fn app(cx: Scope) -> Element {
|
||||
/// cx.render(rsx!{
|
||||
/// div { "Hello World" }
|
||||
/// })
|
||||
/// }
|
||||
///
|
||||
/// async fn main() {
|
||||
/// let mut dom = VirtualDom::new(App);
|
||||
/// let dom = VirtualDom::new(app);
|
||||
///
|
||||
/// let mut inital_edits = dom.rebuild();
|
||||
/// apply_edits(inital_edits);
|
||||
/// real_dom.apply(dom.rebuild());
|
||||
///
|
||||
/// loop {
|
||||
/// dom.wait_for_work().await;
|
||||
/// let frame_timeout = TimeoutFuture::new(Duration::from_millis(16));
|
||||
/// let deadline = || (&mut frame_timeout).now_or_never();
|
||||
/// let edits = dom.run_with_deadline(deadline).await;
|
||||
/// apply_edits(edits);
|
||||
/// loop {
|
||||
/// select! {
|
||||
/// _ = dom.wait_for_work() => {}
|
||||
/// evt = real_dom.wait_for_event() => dom.handle_event(evt),
|
||||
/// }
|
||||
///
|
||||
/// real_dom.apply(dom.render_immediate());
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// ## Waiting for suspense
|
||||
///
|
||||
/// Because Dioxus supports suspense, you can use it for server-side rendering, static site generation, and other usecases
|
||||
/// where waiting on portions of the UI to finish rendering is important. To wait for suspense, use the
|
||||
/// [`VirtualDom::render_with_deadline`] method:
|
||||
///
|
||||
/// ```rust
|
||||
/// let dom = VirtualDom::new(app);
|
||||
///
|
||||
/// let deadline = tokio::time::sleep(Duration::from_millis(100));
|
||||
/// let edits = dom.render_with_deadline(deadline).await;
|
||||
/// ```
|
||||
///
|
||||
/// ## Use with streaming
|
||||
///
|
||||
/// If not all rendering is done by the deadline, it might be worthwhile to stream the rest later. To do this, we
|
||||
/// suggest rendering with a deadline, and then looping between [`VirtualDom::wait_for_work`] and render_immediate until
|
||||
/// no suspended work is left.
|
||||
///
|
||||
/// ```
|
||||
/// let dom = VirtualDom::new(app);
|
||||
///
|
||||
/// let deadline = tokio::time::sleep(Duration::from_millis(20));
|
||||
/// let edits = dom.render_with_deadline(deadline).await;
|
||||
///
|
||||
/// real_dom.apply(edits);
|
||||
///
|
||||
/// while dom.has_suspended_work() {
|
||||
/// dom.wait_for_work().await;
|
||||
/// real_dom.apply(dom.render_immediate());
|
||||
/// }
|
||||
/// ```
|
||||
pub struct VirtualDom {
|
||||
@@ -186,27 +218,7 @@ impl VirtualDom {
|
||||
where
|
||||
P: 'static,
|
||||
{
|
||||
let channel = futures_channel::mpsc::unbounded();
|
||||
Self::new_with_props_and_scheduler(root, root_props, channel)
|
||||
}
|
||||
|
||||
/// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler
|
||||
///
|
||||
/// 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, ignore
|
||||
/// let channel = futures_channel::mpsc::unbounded();
|
||||
/// let dom = VirtualDom::new_with_scheduler(Example, (), channel);
|
||||
/// ```
|
||||
pub fn new_with_props_and_scheduler<P: 'static>(
|
||||
root: fn(Scope<P>) -> Element,
|
||||
root_props: P,
|
||||
(tx, rx): (
|
||||
UnboundedSender<SchedulerMsg>,
|
||||
UnboundedReceiver<SchedulerMsg>,
|
||||
),
|
||||
) -> Self {
|
||||
let (tx, rx) = futures_channel::mpsc::unbounded();
|
||||
let mut dom = Self {
|
||||
rx,
|
||||
scheduler: Scheduler::new(tx),
|
||||
@@ -220,18 +232,138 @@ impl VirtualDom {
|
||||
finished_fibers: Vec::new(),
|
||||
};
|
||||
|
||||
dom.new_scope(Box::into_raw(Box::new(VComponentProps::new(
|
||||
let root = dom.new_scope(Box::into_raw(Box::new(VComponentProps::new(
|
||||
root,
|
||||
|_, _| unreachable!(),
|
||||
root_props,
|
||||
))))
|
||||
))));
|
||||
|
||||
// The root component is always a suspense boundary for any async children
|
||||
// This could be unexpected, so we might rethink this behavior
|
||||
.provide_context(SuspenseBoundary::new(ScopeId(0)));
|
||||
root.provide_context(SuspenseBoundary::new(ScopeId(0)));
|
||||
|
||||
// the root element is always given element 0
|
||||
dom.elements.insert(ElementPath::null());
|
||||
|
||||
dom
|
||||
}
|
||||
|
||||
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
|
||||
self.scopes.get(id.0)
|
||||
}
|
||||
|
||||
pub fn base_scope(&self) -> &ScopeState {
|
||||
self.scopes.get(0).unwrap()
|
||||
}
|
||||
|
||||
fn mark_dirty_scope(&mut self, id: ScopeId) {
|
||||
let height = self.scopes[id.0].height;
|
||||
self.dirty_scopes.insert(DirtyScope { height, id });
|
||||
}
|
||||
|
||||
fn is_scope_suspended(&self, id: ScopeId) -> bool {
|
||||
!self.scopes[id.0]
|
||||
.consume_context::<SuspenseContext>()
|
||||
.unwrap()
|
||||
.borrow()
|
||||
.waiting_on
|
||||
.is_empty()
|
||||
}
|
||||
|
||||
/// Returns true if there is any suspended work left to be done.
|
||||
pub fn has_suspended_work(&self) -> bool {
|
||||
!self.scheduler.leaves.borrow().is_empty()
|
||||
}
|
||||
|
||||
/// Returns None if no element could be found
|
||||
pub fn handle_event<T: 'static>(&mut self, event: &UiEvent<T>) -> Option<()> {
|
||||
let path = self.elements.get(event.element.0)?;
|
||||
|
||||
let location = unsafe { &mut *path.template }
|
||||
.dynamic_attrs
|
||||
.iter()
|
||||
.position(|attr| attr.mounted_element.get() == event.element)?;
|
||||
|
||||
let mut index = Some((path.template, location));
|
||||
|
||||
let mut listeners = Vec::<&Attribute>::new();
|
||||
|
||||
while let Some((raw_parent, dyn_index)) = index {
|
||||
let parent = unsafe { &mut *raw_parent };
|
||||
let path = parent.template.node_paths[dyn_index];
|
||||
|
||||
listeners.extend(
|
||||
parent
|
||||
.dynamic_attrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(idx, attr)| {
|
||||
match is_path_ascendant(parent.template.node_paths[idx], path) {
|
||||
true if attr.name == event.name => Some(attr),
|
||||
_ => None,
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
index = parent.parent;
|
||||
}
|
||||
|
||||
for listener in listeners {
|
||||
if let AttributeValue::Listener(listener) = &listener.value {
|
||||
(listener.borrow_mut())(&event.event)
|
||||
}
|
||||
}
|
||||
|
||||
Some(())
|
||||
}
|
||||
|
||||
/// Wait for the scheduler to have any work.
|
||||
///
|
||||
/// This method polls the internal future queue, waiting for suspense nodes, tasks, or other work. This completes when
|
||||
/// any work is ready. If multiple scopes are marked dirty from a task or a suspense tree is finished, this method
|
||||
/// will exit.
|
||||
///
|
||||
/// This method is cancel-safe, so you're fine to discard the future in a select block.
|
||||
///
|
||||
/// 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) {
|
||||
let mut some_msg = None;
|
||||
|
||||
loop {
|
||||
match some_msg.take() {
|
||||
// If a bunch of messages are ready in a sequence, try to pop them off synchronously
|
||||
Some(msg) => match msg {
|
||||
SchedulerMsg::Immediate(id) => self.mark_dirty_scope(id),
|
||||
SchedulerMsg::TaskNotified(task) => self.handle_task_wakeup(task),
|
||||
SchedulerMsg::SuspenseNotified(id) => self.handle_suspense_wakeup(id),
|
||||
},
|
||||
|
||||
// If they're not ready, then we should wait for them to be ready
|
||||
None => {
|
||||
match self.rx.try_next() {
|
||||
Ok(Some(val)) => some_msg = Some(val),
|
||||
Ok(None) => return,
|
||||
Err(_) => {
|
||||
// If we have any dirty scopes, or finished fiber trees then we should exit
|
||||
if !self.dirty_scopes.is_empty() || !self.finished_fibers.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
some_msg = self.rx.next().await
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -268,22 +400,71 @@ impl VirtualDom {
|
||||
mutations
|
||||
}
|
||||
|
||||
/// Render whatever the VirtualDom has ready as fast as possible without requiring an executor to progress
|
||||
/// suspended subtrees.
|
||||
pub fn render_immediate(&mut self) -> Mutations {
|
||||
// Build a waker that won't wake up since our deadline is already expired when it's polled
|
||||
let waker = futures_util::task::noop_waker();
|
||||
let mut cx = std::task::Context::from_waker(&waker);
|
||||
|
||||
// Now run render with deadline but dont even try to poll any async tasks
|
||||
let fut = self.render_with_deadline(std::future::ready(()));
|
||||
pin_mut!(fut);
|
||||
match fut.poll_unpin(&mut cx) {
|
||||
std::task::Poll::Ready(mutations) => mutations,
|
||||
std::task::Poll::Pending => panic!("render_immediate should never return pending"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Render what you can given the timeline and then move on
|
||||
///
|
||||
/// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues.
|
||||
pub async fn render_with_deadline(
|
||||
&mut self,
|
||||
///
|
||||
/// If no suspense trees are present
|
||||
pub async fn render_with_deadline<'a>(
|
||||
&'a mut self,
|
||||
deadline: impl Future<Output = ()>,
|
||||
) -> Vec<Mutation> {
|
||||
todo!()
|
||||
}
|
||||
) -> Mutations<'a> {
|
||||
use futures_util::future::{select, Either};
|
||||
|
||||
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
|
||||
self.scopes.get(id.0)
|
||||
}
|
||||
let mut mutations = Mutations::new(0);
|
||||
pin_mut!(deadline);
|
||||
|
||||
pub fn base_scope(&self) -> &ScopeState {
|
||||
self.scopes.get(0).unwrap()
|
||||
loop {
|
||||
// first, unload any complete suspense trees
|
||||
for finished_fiber in self.finished_fibers.drain(..) {
|
||||
let scope = &mut self.scopes[finished_fiber.0];
|
||||
let context = scope.has_context::<SuspenseContext>().unwrap();
|
||||
let mut context = context.borrow_mut();
|
||||
mutations.extend(context.mutations.drain(..));
|
||||
}
|
||||
|
||||
// Next, diff any dirty scopes
|
||||
// We choose not to poll the deadline since we complete pretty quickly anyways
|
||||
if let Some(dirty) = self.dirty_scopes.iter().next().cloned() {
|
||||
self.dirty_scopes.remove(&dirty);
|
||||
|
||||
// if the scope is currently suspended, then we should skip it, ignoring any tasks calling for an update
|
||||
if !self.is_scope_suspended(dirty.id) {
|
||||
self.run_scope(dirty.id);
|
||||
self.diff_scope(&mut mutations, dirty.id);
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for suspense, or a deadline
|
||||
if self.dirty_scopes.is_empty() {
|
||||
if self.scheduler.leaves.borrow().is_empty() {
|
||||
return mutations;
|
||||
}
|
||||
|
||||
let (work, deadline) = (self.wait_for_work(), &mut deadline);
|
||||
pin_mut!(work);
|
||||
|
||||
if let Either::Left((_, _)) = select(deadline, work).await {
|
||||
return mutations;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
use dioxus_core::prelude::*;
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
todo!();
|
||||
// render! {
|
||||
// Suspend {
|
||||
// delay: Duration::from_millis(100),
|
||||
// fallback: rsx! { "Loading..." },
|
||||
// ChildAsync {}
|
||||
// ChildAsync {}
|
||||
// ChildAsync {}
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
async fn ChildAsync(cx: Scope<'_>) -> Element {
|
||||
todo!()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let mut mutations = dom.rebuild();
|
||||
}
|
||||
+18
-15
@@ -6,26 +6,29 @@ use dioxus_core::*;
|
||||
async fn it_works() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let mutations = dom.rebuild();
|
||||
let _ = dom.rebuild();
|
||||
|
||||
println!("mutations: {:?}", mutations);
|
||||
|
||||
dom.wait_for_work().await;
|
||||
tokio::select! {
|
||||
_ = dom.wait_for_work() => {}
|
||||
_ = tokio::time::sleep(Duration::from_millis(1000)) => {}
|
||||
};
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.spawn(async {
|
||||
for x in 0..10 {
|
||||
tokio::time::sleep(Duration::from_millis(500)).await;
|
||||
println!("Hello, world! {x}");
|
||||
}
|
||||
});
|
||||
cx.use_hook(|| {
|
||||
cx.spawn(async {
|
||||
for x in 0..10 {
|
||||
tokio::time::sleep(Duration::from_millis(50)).await;
|
||||
println!("Hello, world! {x}");
|
||||
}
|
||||
});
|
||||
|
||||
cx.spawn(async {
|
||||
for x in 0..10 {
|
||||
tokio::time::sleep(Duration::from_millis(250)).await;
|
||||
println!("Hello, world does! {x}");
|
||||
}
|
||||
cx.spawn(async {
|
||||
for x in 0..10 {
|
||||
tokio::time::sleep(Duration::from_millis(25)).await;
|
||||
println!("Hello, world from second thread! {x}");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
None
|
||||
|
||||
@@ -35,9 +35,6 @@ env_logger = "0.9.0"
|
||||
tokio = { version = "1.21.2", features = ["full"] }
|
||||
# dioxus-edit-stream = { path = "../edit-stream" }
|
||||
|
||||
[[bench]]
|
||||
name = "create"
|
||||
harness = false
|
||||
|
||||
[[bench]]
|
||||
name = "jsframework"
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
fn main() {}
|
||||
@@ -5,9 +5,14 @@
|
||||
//! to be made, but the change application phase will be just as performant as the vanilla wasm_bindgen code. In essence,
|
||||
//! we are measuring the overhead of Dioxus, not the performance of the "apply" phase.
|
||||
//!
|
||||
//! On my MBP 2019:
|
||||
//! - Dioxus takes 3ms to create 1_000 rows
|
||||
//! - Dioxus takes 30ms to create 10_000 rows
|
||||
//!
|
||||
//! Pre-templates (Mac M1):
|
||||
//! - 3ms to create 1_000 rows
|
||||
//! - 30ms to create 10_000 rows
|
||||
//!
|
||||
//! Post-templates
|
||||
//! - 580us to create 1_000 rows
|
||||
//! - 6.2ms to create 10_000 rows
|
||||
//!
|
||||
//! As pure "overhead", these are amazing good numbers, mostly slowed down by hitting the global allocator.
|
||||
//! These numbers don't represent Dioxus with the heuristic engine installed, so I assume it'll be even faster.
|
||||
@@ -20,25 +25,26 @@ criterion_group!(mbenches, create_rows);
|
||||
criterion_main!(mbenches);
|
||||
|
||||
fn create_rows(c: &mut Criterion) {
|
||||
static App: Component = |cx| {
|
||||
fn app(cx: Scope) -> Element {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
|
||||
render!(table {
|
||||
tbody {
|
||||
(0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
rsx!(Row {
|
||||
row_id: f,
|
||||
label: label
|
||||
render!(
|
||||
table {
|
||||
tbody {
|
||||
(0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
rsx!( Row { row_id: f, label: label } )
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
c.bench_function("create rows", |b| {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
dom.rebuild();
|
||||
|
||||
b.iter(|| {
|
||||
let mut dom = VirtualDom::new(App);
|
||||
let g = dom.rebuild();
|
||||
assert!(g.edits.len() > 1);
|
||||
})
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
use dioxus::prelude::*;
|
||||
use rand::prelude::*;
|
||||
|
||||
fn main() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
dom.rebuild();
|
||||
|
||||
for _ in 0..1000 {
|
||||
dom.rebuild();
|
||||
}
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
let mut rng = SmallRng::from_entropy();
|
||||
|
||||
render! (
|
||||
table {
|
||||
tbody {
|
||||
(0..10_000_usize).map(|f| {
|
||||
let label = Label::new(&mut rng);
|
||||
rsx!( Row { row_id: f, label: label } )
|
||||
})
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Props)]
|
||||
struct RowProps {
|
||||
row_id: usize,
|
||||
label: Label,
|
||||
}
|
||||
fn Row(cx: Scope<RowProps>) -> Element {
|
||||
let [adj, col, noun] = cx.props.label.0;
|
||||
render! {
|
||||
tr {
|
||||
td { class:"col-md-1", "{cx.props.row_id}" }
|
||||
td { class:"col-md-1", onclick: move |_| { /* run onselect */ },
|
||||
a { class: "lbl", "{adj}" "{col}" "{noun}" }
|
||||
}
|
||||
td { class: "col-md-1",
|
||||
a { class: "remove", onclick: move |_| {/* remove */},
|
||||
span { class: "glyphicon glyphicon-remove remove", aria_hidden: "true" }
|
||||
}
|
||||
}
|
||||
td { class: "col-md-6" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct Label([&'static str; 3]);
|
||||
|
||||
impl Label {
|
||||
fn new(rng: &mut SmallRng) -> Self {
|
||||
Label([
|
||||
ADJECTIVES.choose(rng).unwrap(),
|
||||
COLOURS.choose(rng).unwrap(),
|
||||
NOUNS.choose(rng).unwrap(),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
static ADJECTIVES: &[&str] = &[
|
||||
"pretty",
|
||||
"large",
|
||||
"big",
|
||||
"small",
|
||||
"tall",
|
||||
"short",
|
||||
"long",
|
||||
"handsome",
|
||||
"plain",
|
||||
"quaint",
|
||||
"clean",
|
||||
"elegant",
|
||||
"easy",
|
||||
"angry",
|
||||
"crazy",
|
||||
"helpful",
|
||||
"mushy",
|
||||
"odd",
|
||||
"unsightly",
|
||||
"adorable",
|
||||
"important",
|
||||
"inexpensive",
|
||||
"cheap",
|
||||
"expensive",
|
||||
"fancy",
|
||||
];
|
||||
|
||||
static COLOURS: &[&str] = &[
|
||||
"red", "yellow", "blue", "green", "pink", "brown", "purple", "brown", "white", "black",
|
||||
"orange",
|
||||
];
|
||||
|
||||
static NOUNS: &[&str] = &[
|
||||
"table", "chair", "house", "bbq", "desk", "car", "pony", "cookie", "sandwich", "burger",
|
||||
"pizza", "mouse", "keyboard",
|
||||
];
|
||||
@@ -1,5 +1,3 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
||||
let asd = 123;
|
||||
let var = 123;
|
||||
@@ -23,8 +21,63 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
||||
})
|
||||
}
|
||||
|
||||
// imports come after the test since the rsx! macro is sensitive to its location in the file
|
||||
// (the byte index is used to differentiate sub templates)
|
||||
use dioxus::core::{ElementId, Mutation};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn dual_stream() {
|
||||
let mut dom = VirtualDom::new(basic_syntax_is_a_template);
|
||||
dbg!(dom.rebuild());
|
||||
let edits = dom.rebuild();
|
||||
|
||||
use Mutation::*;
|
||||
assert_eq!(
|
||||
edits.template_mutations,
|
||||
vec![
|
||||
CreateElement { name: "div", namespace: None, id: ElementId(1) },
|
||||
SetAttribute { name: "class", value: "asd", id: ElementId(1) },
|
||||
CreateElement { name: "div", namespace: None, id: ElementId(2) },
|
||||
CreatePlaceholder { id: ElementId(3) },
|
||||
AppendChildren { m: 1 },
|
||||
CreateElement { name: "div", namespace: None, id: ElementId(4) },
|
||||
CreateElement { name: "h1", namespace: None, id: ElementId(5) },
|
||||
CreateText { value: "var" },
|
||||
AppendChildren { m: 1 },
|
||||
CreateElement { name: "p", namespace: None, id: ElementId(6) },
|
||||
CreateText { value: "you're great!" },
|
||||
AppendChildren { m: 1 },
|
||||
CreateElement { name: "div", namespace: None, id: ElementId(7) },
|
||||
SetAttribute { name: "background-color", value: "red", id: ElementId(7) },
|
||||
CreateElement { name: "h1", namespace: None, id: ElementId(8) },
|
||||
CreateText { value: "var" },
|
||||
AppendChildren { m: 1 },
|
||||
CreateElement { name: "div", namespace: None, id: ElementId(9) },
|
||||
CreateElement { name: "b", namespace: None, id: ElementId(10) },
|
||||
CreateText { value: "asd" },
|
||||
AppendChildren { m: 1 },
|
||||
CreateText { value: "not great" },
|
||||
AppendChildren { m: 2 },
|
||||
AppendChildren { m: 2 },
|
||||
CreateElement { name: "p", namespace: None, id: ElementId(11) },
|
||||
CreateText { value: "you're great!" },
|
||||
AppendChildren { m: 1 },
|
||||
AppendChildren { m: 4 },
|
||||
AppendChildren { m: 2 },
|
||||
SaveTemplate { name: "packages/dioxus/tests/rsx_syntax.rs:5:15:122", m: 1 }
|
||||
]
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
vec![
|
||||
LoadTemplate { name: "packages/dioxus/tests/rsx_syntax.rs:5:15:122", index: 0 },
|
||||
AssignId { path: &[], id: ElementId(12) },
|
||||
SetAttribute { name: "class", value: "123", id: ElementId(12) },
|
||||
SetAttribute { name: "onclick", value: "asd", id: ElementId(12) }, // ---- todo: listeners
|
||||
HydrateText { path: &[0, 0], value: "123", id: ElementId(13) },
|
||||
ReplacePlaceholder { m: 1, path: &[0, 0] },
|
||||
AppendChildren { m: 1 }
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::future::IntoFuture;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_core::SuspenseBoundary;
|
||||
|
||||
#[inline_props]
|
||||
fn suspense_boundary<'a>(cx: Scope<'a>, children: Element<'a>) -> Element {
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
fn main() {}
|
||||
Reference in new issue
Block a user