examples: upgrade to new version of dioxus core.

also add the inline_props macro
This commit is contained in:
Jonathan Kelley committed 2021-12-25 17:18:05 -05:00
1 parent 21e00c114e
commit cda759c659
41 files changed
+556 -253

No files matched your search

-1
View File
@@ -52,7 +52,6 @@ fern = { version = "0.6.0", features = ["colored"] }
rand = { version = "0.8.4", features = ["small_rng"] }
simple_logger = "1.13.0"
dioxus-core-macro = { path = "../core-macro", version = "^0.1.2" }
# dioxus-hooks = { path = "../hooks" }
criterion = "0.3.5"
thiserror = "1.0.30"
+1 -3
View File
@@ -46,11 +46,9 @@ fn main() {
VNodes::Element(vel) => dbg!(vel),
_ => todo!()
}
}
```
## Internals
Dioxus-core builds off the many frameworks that came before it. Notably, Dioxus borrows these concepts:
+4 -4
View File
@@ -5,11 +5,11 @@ use dioxus_html as dioxus_elements;
fn main() {}
fn App(cx: Scope<()>) -> Element {
fn app(cx: Scope) -> Element {
cx.render(rsx!(div {
App2 {
app2 (
p: "asd"
}
)
}))
}
@@ -18,7 +18,7 @@ struct Borrowed<'a> {
p: &'a str,
}
fn App2<'a>(cx: Scope<'a, Borrowed<'a>>) -> Element {
fn app2<'a>(cx: Scope<'a, Borrowed<'a>>) -> Element {
let g = eat2(&cx);
rsx!(cx, "")
}
+5 -5
View File
@@ -1,6 +1,6 @@
//! This module contains the stateful DiffMachine and all methods to diff VNodes, their properties, and their children.
//! This module contains the stateful DiffState and all methods to diff VNodes, their properties, and their children.
//!
//! The [`DiffMachine`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
//! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
//! of mutations for the RealDom to apply.
//!
//! ## Notice:
@@ -86,14 +86,14 @@
//! - Certain web-dom-specific optimizations.
//!
//! More info on how to improve this diffing algorithm:
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
//! - <https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/>
use crate::innerlude::*;
use fxhash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
use DomEdit::*;
/// Our DiffMachine is an iterative tree differ.
/// Our DiffState is an iterative tree differ.
///
/// It uses techniques of a stack machine to allow pausing and restarting of the diff algorithm. This
/// was originally implemented using recursive techniques, but Rust lacks the ability to call async functions recursively,
@@ -490,7 +490,7 @@ impl<'bump> DiffState<'bump> {
(Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
// The normal pathway still works, but generates slightly weird instructions
// This pathway just ensures we get create and replace
// This pathway ensures uses the ReplaceAll, not the InsertAfter and remove
(Placeholder(_), Fragment(new)) => {
self.stack
.create_children(new.children, MountType::Replace { old: old_node });
+8 -11
View File
@@ -8,10 +8,10 @@
//! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case,
//! we build a tiny alloactor in the stack and allocate the closure into that.
//!
//! The logic for this was borrowed from https://docs.rs/stack_dst/0.6.1/stack_dst/. Unfortunately, this crate does not
//! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
//! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
use crate::prelude::{NodeFactory, VNode};
use crate::innerlude::{NodeFactory, VNode};
use std::mem;
/// A concrete type provider for closures that build VNode structures.
@@ -35,15 +35,15 @@ enum StackNodeStorage<'a, 'b> {
}
impl<'a, 'b> LazyNodes<'a, 'b> {
pub fn new_some<F>(_val: F) -> Option<Self>
pub fn new_some<F>(_val: F) -> Self
where
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
{
Some(Self::new(_val))
Self::new(_val)
}
/// force this call onto the stack
pub fn new_boxed<F>(_val: F) -> Option<Self>
pub fn new_boxed<F>(_val: F) -> Self
where
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
{
@@ -55,9 +55,9 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
fac.map(inner)
};
Some(Self {
Self {
inner: StackNodeStorage::Heap(Box::new(val)),
})
}
}
pub fn new<F>(_val: F) -> Self
@@ -249,9 +249,7 @@ fn it_works() {
let caller = LazyNodes::new_some(|f| {
//
f.text(format_args!("hello world!"))
})
.unwrap();
});
let g = caller.call(factory);
dbg!(g);
@@ -291,7 +289,6 @@ fn it_drops() {
log::debug!("main closure");
f.fragment_from_iter(it)
})
.unwrap()
};
let _ = caller.call(factory);
+9 -3
View File
@@ -57,19 +57,25 @@ pub(crate) mod innerlude {
/// // ...
/// };
/// ```
pub type Component<P> = fn(Scope<P>) -> Element;
pub type Component<P = ()> = fn(Scope<P>) -> Element;
/// A list of attributes
///
pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
}
pub use crate::innerlude::{
Attribute, Component, DioxusElement, DomEdit, Element, ElementId, EventHandler, EventPriority,
IntoVNode, LazyNodes, Listener, Mutations, NodeFactory, Properties, SchedulerMsg, Scope,
ScopeId, ScopeState, UserEvent, VElement, VFragment, VNode, VirtualDom,
ScopeId, ScopeState, UserEvent, VComponent, VElement, VFragment, VNode, VPlaceholder, VText,
VirtualDom,
};
pub mod prelude {
pub use crate::innerlude::Scope;
pub use crate::innerlude::{
Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory, ScopeState,
Attributes, Component, DioxusElement, Element, EventHandler, LazyNodes, NodeFactory,
ScopeState,
};
pub use crate::nodes::VNode;
pub use crate::virtual_dom::{fc_to_builder, Fragment, Properties};
+3
View File
@@ -1,6 +1,9 @@
//! Instructions returned by the VirtualDOM on how to modify the Real DOM.
//!
//! This module contains an internal API to generate these instructions.
//!
//! Beware that changing code in this module will break compatibility with
//! interpreters for these types of DomEdits.
use crate::innerlude::*;
use std::{any::Any, fmt::Debug};
+11 -3
View File
@@ -20,7 +20,7 @@ use std::{
///
/// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of:
///
/// - the [`rsx`] macro
/// - the `rsx!` macro
/// - the [`NodeFactory`] API
pub enum VNode<'src> {
/// Text VNodes simply bump-allocated (or static) string slices
@@ -51,7 +51,7 @@ pub enum VNode<'src> {
/// key: "a",
/// onclick: |e| log::info!("clicked"),
/// hidden: "true",
/// style: { background_color: "red" }
/// style: { background_color: "red" },
/// "hello"
/// }
/// });
@@ -59,7 +59,7 @@ pub enum VNode<'src> {
/// if let VNode::Element(velement) = node {
/// assert_eq!(velement.tag_name, "div");
/// assert_eq!(velement.namespace, None);
/// assert_eq!(velement.key, Some("a));
/// assert_eq!(velement.key, Some("a"));
/// }
/// ```
Element(&'src VElement<'src>),
@@ -720,6 +720,14 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
}
}
impl<'a, 'b> IntoIterator for LazyNodes<'a, 'b> {
type Item = LazyNodes<'a, 'b>;
type IntoIter = std::iter::Once<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
std::iter::once(self)
}
}
impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
self.call(cx)
+37 -16
View File
@@ -351,7 +351,7 @@ impl ScopeArena {
/// cx.render(rsx!{ div {"Hello, {cx.props.name}"} })
/// }
/// ```
pub struct Scope<'a, P> {
pub struct Scope<'a, P = ()> {
pub scope: &'a ScopeState,
pub props: &'a P,
}
@@ -366,6 +366,26 @@ impl<P> Clone for Scope<'_, P> {
}
}
impl<'src, P> Scope<'src, P> {
pub fn register_task(self, task: impl Future<Output = ()> + 'src) {
let fut: &'src mut dyn Future<Output = ()> = self.scope.bump().alloc(task);
let boxed_fut: BumpBox<'src, dyn Future<Output = ()> + 'src> =
unsafe { BumpBox::from_raw(fut) };
let pinned_fut: Pin<BumpBox<'src, _>> = boxed_fut.into();
// erase the 'src lifetime for self-referential storage
// todo: provide a miri test around this
// concerned about segfaulting
let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) };
self.sender
.unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
.unwrap();
self.scope.items.borrow_mut().tasks.push(self_ref_fut);
}
}
impl<'a, P> std::ops::Deref for Scope<'a, P> {
// rust will auto deref again to the original 'a lifetime at the call site
type Target = &'a ScopeState;
@@ -578,7 +598,7 @@ impl ScopeState {
/// Schedule an update for any component given its ScopeId.
///
/// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method.
/// A component's ScopeId can be obtained from `use_hook` or the [`ScopeState::scope_id`] method.
///
/// This method should be used when you want to schedule an update for a component
pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
@@ -662,29 +682,33 @@ impl ScopeState {
/// 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>(&'src self, fut: impl FnOnce() -> F + 'src) -> usize
pub fn push_task<'src, F>(&'src self, fut: F) -> usize
where
F: Future<Output = ()>,
F::Output: 'src,
F: 'src,
{
self.sender
.unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
.unwrap();
// 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();
let fut: &'src mut dyn Future<Output = ()> = self.bump().alloc(fut);
let boxed_fut: BumpBox<'src, dyn Future<Output = ()> + 'src> =
unsafe { BumpBox::from_raw(fut) };
let pinned_fut: Pin<BumpBox<'src, _>> = boxed_fut.into();
// erase the 'src lifetime for self-referential storage
// todo: provide a miri test around this
// concerned about segfaulting
let self_ref_fut = unsafe { std::mem::transmute(pinned_fut) };
let self_ref_fut = unsafe {
std::mem::transmute::<Pin<BumpBox<'src, _>>, Pin<BumpBox<'static, _>>>(pinned_fut)
};
self.sender
.unbounded_send(SchedulerMsg::NewTask(self.our_arena_idx))
.unwrap();
// Push the future into the tasks
let mut items = self.items.borrow_mut();
@@ -707,14 +731,11 @@ impl ScopeState {
/// cx.render(lazy_tree)
/// }
///```
pub fn render<'src>(&'src self, rsx: Option<LazyNodes<'src, '_>>) -> Option<VNode<'src>> {
pub fn render<'src>(&'src self, rsx: LazyNodes<'src, '_>) -> Option<VNode<'src>> {
let fac = NodeFactory {
bump: &self.wip_frame().bump,
};
match rsx {
Some(s) => Some(s.call(fac)),
None => todo!("oh no no nodes"),
}
Some(rsx.call(fac))
}
/// Store a value between renders
+7 -7
View File
@@ -15,7 +15,7 @@ use std::{any::Any, collections::VecDeque, iter::FromIterator, pin::Pin, sync::A
///
/// ## Guide
///
/// Components are defined as simple functions that take [`Context`] and a [`Properties`] type and return an [`Element`].
/// Components are defined as simple functions that take [`Scope`] and return an [`Element`].
///
/// ```rust, ignore
/// #[derive(Props, PartialEq)]
@@ -552,11 +552,11 @@ impl VirtualDom {
/// 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: LazyNodes<'a, '_>) -> &'a VNode<'a> {
let scope = self.scopes.get_scope(ScopeId(0)).unwrap();
let frame = scope.wip_frame();
let factory = NodeFactory { bump: &frame.bump };
let node = lazy_nodes.unwrap().call(factory);
let node = lazy_nodes.call(factory);
frame.bump.alloc(node)
}
@@ -594,7 +594,7 @@ impl VirtualDom {
/// let dom = VirtualDom::new(Base);
/// let nodes = dom.render_nodes(rsx!("div"));
/// ```
pub fn create_vnodes<'a>(&'a self, nodes: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
let mut machine = DiffState::new(&self.scopes);
machine.stack.element_stack.push(ElementId(0));
machine
@@ -619,8 +619,8 @@ impl VirtualDom {
/// ```
pub fn diff_lazynodes<'a>(
&'a self,
left: Option<LazyNodes<'a, '_>>,
right: Option<LazyNodes<'a, '_>>,
left: LazyNodes<'a, '_>,
right: LazyNodes<'a, '_>,
) -> (Mutations<'a>, Mutations<'a>) {
let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
@@ -947,7 +947,7 @@ impl<'a> Properties for FragmentProps<'a> {
#[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(Some(LazyNodes::new(|f| f.fragment_from_iter(i))))
cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
}
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
+6 -6
View File
@@ -21,7 +21,7 @@ fn new_dom<P: 'static + Send>(app: Component<P>, props: P) -> VirtualDom {
#[test]
fn test_original_diff() {
static APP: Component<()> = |cx| {
static APP: Component = |cx| {
cx.render(rsx! {
div {
div {
@@ -57,7 +57,7 @@ fn test_original_diff() {
#[test]
fn create() {
static APP: Component<()> = |cx| {
static APP: Component = |cx| {
cx.render(rsx! {
div {
div {
@@ -120,7 +120,7 @@ fn create() {
#[test]
fn create_list() {
static APP: Component<()> = |cx| {
static APP: Component = |cx| {
cx.render(rsx! {
{(0..3).map(|f| rsx!{ div {
"hello"
@@ -169,7 +169,7 @@ fn create_list() {
#[test]
fn create_simple() {
static APP: Component<()> = |cx| {
static APP: Component = |cx| {
cx.render(rsx! {
div {}
div {}
@@ -207,7 +207,7 @@ fn create_simple() {
}
#[test]
fn create_components() {
static App: Component<()> = |cx| {
static App: Component = |cx| {
cx.render(rsx! {
Child { "abc1" }
Child { "abc2" }
@@ -273,7 +273,7 @@ fn create_components() {
}
#[test]
fn anchors() {
static App: Component<()> = |cx| {
static App: Component = |cx| {
cx.render(rsx! {
{true.then(|| rsx!{ div { "hello" } })}
{false.then(|| rsx!{ div { "goodbye" } })}
+65
View File
@@ -802,3 +802,68 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
]
);
}
// noticed some weird behavior in the desktop interpreter
// just making sure it doesnt happen in the core implementation
#[test]
fn remove_list() {
let dom = new_dom();
let left = rsx!({
(0..10).rev().take(5).map(|i| {
rsx! { Fragment { key: "{i}", "{i}" }}
})
});
let right = rsx!({
(0..10).rev().take(2).map(|i| {
rsx! { Fragment { key: "{i}", "{i}" }}
})
});
let (create, changes) = dom.diff_lazynodes(left, right);
// dbg!(create);
// dbg!(changes);
assert_eq!(
changes.edits,
[
// remove 5, 4, 3
Remove { root: 3 },
Remove { root: 4 },
Remove { root: 5 },
]
);
}
// noticed some weird behavior in the desktop interpreter
// just making sure it doesnt happen in the core implementation
#[test]
fn remove_list_nokeyed() {
let dom = new_dom();
let left = rsx!({
(0..10).rev().take(5).map(|i| {
rsx! { Fragment { "{i}" }}
})
});
let right = rsx!({
(0..10).rev().take(2).map(|i| {
rsx! { Fragment { "{i}" }}
})
});
let (create, changes) = dom.diff_lazynodes(left, right);
assert_eq!(
changes.edits,
[
// remove 5, 4, 3
Remove { root: 3 },
Remove { root: 4 },
Remove { root: 5 },
]
);
}