wip: fill out the snippets

This commit is contained in:
Jonathan Kelley committed 2021-07-20 19:03:49 -04:00
1 parent 4a72b3140b
commit 6051b0ec86
14 files changed
+185 -66

No files matched your search

+1
View File
@@ -64,3 +64,4 @@ Tokio
asynchronicity asynchronicity
constified constified
SegVec SegVec
contentful
+4 -3
View File
@@ -165,15 +165,16 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
| Custom elements | ✅ | ✅ | Define new element primitives | | Custom elements | ✅ | ✅ | Define new element primitives |
| Suspense | ✅ | ✅ | schedule future render from future/promise | | Suspense | ✅ | ✅ | schedule future render from future/promise |
| Integrated error handling | ✅ | ✅ | Gracefully handle errors with ? syntax | | Integrated error handling | ✅ | ✅ | Gracefully handle errors with ? syntax |
| Effects | 🛠 | ✅ | Run effects after a component has been committed to render | | Re-hydration | 🛠 | ✅ | Pre-render to HTML to speed up first contentful paint |
| Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events | | Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events |
| NodeRef | 🛠 | ✅ | gain direct access to nodes [1] |
| Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) | | Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) |
| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context | | 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context |
| Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees | | Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees |
| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts | | Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts |
| Heuristic Engine | 🛠 | ❓ | track component memory usage to minimize future allocations | | Heuristic Engine | 🛠 | ❓ | track component memory usage to minimize future allocations |
| Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates | | Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates |
| Effects | 🛠 | ✅ | Run effects after a component has been committed to render |
| NodeRef | 🛠 | ✅ | gain direct access to nodes [1] |
- [1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API. - [1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API.
@@ -195,7 +196,7 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
| Feature | Dioxus | React | Notes for Dioxus | | Feature | Dioxus | React | Notes for Dioxus |
| -------------------- | ------ | ----- | ------------------------------------ | | -------------------- | ------ | ----- | ------------------------------------ |
| Portal | ❓ | ✅ | cast elements through tree | | Portal | ❓ | ✅ | cast elements through tree |
| Error/Panic boundary | ❓ | ✅ | catch panics and display custom BSOD | | Error/Panic boundary | 👀 | ✅ | catch panics and display custom BSOD |
| Code-splitting | 👀 | ✅ | Make bundle smaller/lazy | | Code-splitting | 👀 | ✅ | Make bundle smaller/lazy |
| LiveView | 👀 | ❓ | Example for SSR + WASM apps | | LiveView | 👀 | ❓ | Example for SSR + WASM apps |
+85
View File
@@ -0,0 +1,85 @@
//! Example: Errror Handling
//! ------------------------
//!
//! Error handling in Dioxus comes in a few flavors. Because Dioxus is a Rust project, Options and Results are obviously
//! the go-to way of wrapping possibly-errored data. However, if a component fails to "unwrapping," everything will crash,
//! the page will deadlock, and your users will be sad.
//!
//! So, obviously, you need to handle your errors.
//!
//! Fortunately, it's easy to avoid panics, even during quick prototyping.
//!
//! Here's a few strategies:
//! - Leverage the ability to return "None" and propogate None directly
//! - Instead of propogating "None" manually, use the "?" syntax sugar
//! - Covert Results into Options with .ok()
//! - Manually display a separate screen by matching on Options/Results
//!
//! There *are* plans to add helpful screens for when apps completely panic in WASM. However, you should really try to
//! avoid panicking.
use dioxus::prelude::*;
fn main() {}
/// This is one way to go about error handling (just toss things away with unwrap).
/// However, if you get it wrong, the whole app will crash.
/// This is pretty flimsy.
static App: FC<()> = |cx| {
let data = get_data().unwrap();
cx.render(rsx!( div { "{data}" } ))
};
/// This is a pretty verbose way of error handling
/// However, it's still pretty good since we don't panic, just fail to render anything
static App1: FC<()> = |cx| {
let data = match get_data() {
Some(data) => data,
None => return None,
};
cx.render(rsx!( div { "{data}" } ))
};
/// This is an even better form of error handling.
/// However, it _does_ make the component go blank, which might not be desirable.
///
/// This type of error handling is good when you have "selectors" that produce Some/None based on some state that's
/// already controlled for higher in the tree. IE displaying a "Username" in a component that should only be shown if
/// a user is logged in.
///
/// Dioxus will throw an error in the console if the None-path is ever taken.
static App2: FC<()> = |cx| {
let data = get_data()?;
cx.render(rsx!( div { "{data}" } ))
};
/// This is top-tier error handling since it displays a failure state.
///
/// However, the error is lacking in context.
static App3: FC<()> = |cx| match get_data() {
Some(data) => cx.render(rsx!( div { "{data}" } )),
None => cx.render(rsx!( div { "Failed to load data :(" } )),
};
/// For errors that return results, it's possible short-circuit the match-based error handling with `.ok()` which converts
/// a Result<T, V> into an Option<T> and lets you
static App4: FC<()> = |cx| {
let data = get_data_err().ok()?;
cx.render(rsx!( div { "{data}" } ))
};
/// This is great error handling since it displays a failure state... with context!
///
/// Hopefully you never need to disply a screen like this. It's rather bad taste
static App5: FC<()> = |cx| match get_data_err() {
Ok(data) => cx.render(rsx!( div { "{data}" } )),
Err(c) => cx.render(rsx!( div { "Failed to load data: {c}" } )),
};
// this fetching function produces "nothing"
fn get_data() -> Option<String> {
None
}
// this fetching function produces "nothing"
fn get_data_err() -> Result<String, &'static str> {
Result::Err("Failed!")
}
+1 -1
View File
@@ -12,7 +12,7 @@ description = "Core macro for Dioxus Virtual DOM"
proc-macro = true proc-macro = true
[dependencies] [dependencies]
once_cell = "1.7.2" once_cell = "1.8"
proc-macro2 = { version = "1.0.6" } proc-macro2 = { version = "1.0.6" }
quote = "1.0" quote = "1.0"
syn = { version = "1.0.11", features = ["full", "extra-traits"] } syn = { version = "1.0.11", features = ["full", "extra-traits"] }
+2 -2
View File
@@ -1,13 +1,13 @@
use std::{cell::UnsafeCell, rc::Rc}; use std::{cell::UnsafeCell, rc::Rc};
use crate::innerlude::*; use crate::innerlude::*;
use slotmap::{DefaultKey, SlotMap}; use slotmap::SlotMap;
#[derive(Clone)] #[derive(Clone)]
pub struct SharedArena { pub struct SharedArena {
pub components: Rc<UnsafeCell<ScopeMap>>, pub components: Rc<UnsafeCell<ScopeMap>>,
} }
pub type ScopeMap = SlotMap<DefaultKey, Scope>; pub type ScopeMap = SlotMap<ScopeId, Scope>;
enum MutStatus { enum MutStatus {
Immut, Immut,
+8 -7
View File
@@ -77,13 +77,14 @@ impl ActiveFrame {
} }
} }
pub fn next(&mut self) -> &mut BumpFrame { pub fn old_frame_mut(&mut self) -> &mut BumpFrame {
*self.generation.borrow_mut() += 1; match *self.generation.borrow() & 1 == 0 {
true => &mut self.frames[1],
if *self.generation.borrow() % 2 == 0 { false => &mut self.frames[0],
&mut self.frames[0]
} else {
&mut self.frames[1]
} }
} }
pub fn cycle_frame(&mut self) {
*self.generation.borrow_mut() += 1;
}
} }
+26 -28
View File
@@ -115,6 +115,11 @@ impl<'src, P> Context<'src, P> {
todo!() todo!()
} }
/// Get's this context's ScopeId
pub fn get_scope_id(&self) -> ScopeId {
self.scope.our_arena_idx.clone()
}
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator. /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
/// ///
/// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned. /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
@@ -200,7 +205,11 @@ Any function prefixed with "use" should not be called conditionally.
/// ///
/// ///
/// ///
pub fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) { pub fn use_context_provider<T, F>(self, init: F) -> &'src Rc<T>
where
T: 'static,
F: FnOnce() -> T,
{
let mut cxs = self.scope.shared_contexts.borrow_mut(); let mut cxs = self.scope.shared_contexts.borrow_mut();
let ty = TypeId::of::<T>(); let ty = TypeId::of::<T>();
@@ -225,7 +234,8 @@ Any function prefixed with "use" should not be called conditionally.
} }
_ => debug_assert!(false, "Cannot initialize two contexts of the same type"), _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
} };
self.use_context::<T>()
} }
/// There are hooks going on here! /// There are hooks going on here!
@@ -234,26 +244,19 @@ Any function prefixed with "use" should not be called conditionally.
} }
/// Uses a context, storing the cached value around /// Uses a context, storing the cached value around
pub fn try_use_context<T: 'static>(self) -> Result<&'src Rc<T>> { ///
/// If a context is not found on the first search, then this call will be "dud", always returning "None" even if a
/// context was added later. This allows using another hook as a fallback
///
pub fn try_use_context<T: 'static>(self) -> Option<&'src Rc<T>> {
struct UseContextHook<C> { struct UseContextHook<C> {
par: Option<Rc<C>>, par: Option<Rc<C>>,
we: Option<Weak<C>>,
} }
self.use_hook( self.use_hook(
move |_| UseContextHook { move |_| {
par: None as Option<Rc<T>>,
we: None as Option<Weak<T>>,
},
move |hook| {
let mut scope = Some(self.scope); let mut scope = Some(self.scope);
let mut par = None;
if let Some(we) = &hook.we {
if let Some(re) = we.upgrade() {
hook.par = Some(re);
return Ok(hook.par.as_ref().unwrap());
}
}
let ty = TypeId::of::<T>(); let ty = TypeId::of::<T>();
while let Some(inner) = scope { while let Some(inner) = scope {
@@ -270,26 +273,21 @@ Any function prefixed with "use" should not be called conditionally.
.downcast::<T>() .downcast::<T>()
.expect("Should not fail, already validated the type from the hashmap"); .expect("Should not fail, already validated the type from the hashmap");
hook.we = Some(Rc::downgrade(&rc)); par = Some(rc);
hook.par = Some(rc); break;
return Ok(hook.par.as_ref().unwrap());
} else { } else {
match inner.parent_idx { match inner.parent_idx {
Some(parent_id) => { Some(parent_id) => {
let parent = inner scope = inner.arena_link.get(parent_id);
.arena_link
.get(parent_id)
.ok_or_else(|| Error::FatalInternal("Failed to find parent"))?;
scope = Some(parent);
} }
None => return Err(Error::MissingSharedContext), None => break,
} }
} }
} }
//
Err(Error::MissingSharedContext) UseContextHook { par }
}, },
move |hook| hook.par.as_ref(),
|_| {}, |_| {},
) )
} }
+3
View File
@@ -19,6 +19,9 @@ pub enum Error {
#[error("Wrong Properties Type")] #[error("Wrong Properties Type")]
WrongProps, WrongProps,
#[error("The component failed to return VNodes")]
ComponentFailed,
#[error("Base scope has not been mounted yet")] #[error("Base scope has not been mounted yet")]
NotMounted, NotMounted,
+1 -1
View File
@@ -10,7 +10,7 @@
//! //!
pub use crate::innerlude::{ pub use crate::innerlude::{
format_args_f, html, rsx, Context, DioxusElement, DomEdit, EventTrigger, LazyNodes, format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, EventTrigger, LazyNodes,
NodeFactory, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom, NodeFactory, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom,
VirtualEvent, FC, VirtualEvent, FC,
}; };
+24
View File
@@ -267,6 +267,22 @@ impl<'a> NodeFactory<'a> {
} }
} }
pub fn attr_with_alloc_val(
&self,
name: &'static str,
val: &'a str,
namespace: Option<&'static str>,
is_volatile: bool,
) -> Attribute<'a> {
Attribute {
name,
value: val,
is_static: false,
namespace,
is_volatile,
}
}
pub fn component<P>( pub fn component<P>(
&self, &self,
component: FC<P>, component: FC<P>,
@@ -508,6 +524,14 @@ impl IntoVNode<'_> for Option<()> {
cx.fragment_from_iter(None as Option<VNode>) cx.fragment_from_iter(None as Option<VNode>)
} }
} }
impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
match self {
Some(n) => n,
None => cx.fragment_from_iter(None as Option<VNode>),
}
}
}
impl Debug for NodeFactory<'_> { impl Debug for NodeFactory<'_> {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+15 -11
View File
@@ -113,7 +113,8 @@ impl Scope {
// Remove all the outdated listeners // Remove all the outdated listeners
// This is a very dangerous operation // This is a very dangerous operation
self.frames.next().bump.reset(); let next_frame = self.frames.old_frame_mut();
next_frame.bump.reset();
self.listeners.borrow_mut().clear(); self.listeners.borrow_mut().clear();
@@ -123,16 +124,19 @@ impl Scope {
// Cast the caller ptr from static to one with our own reference // Cast the caller ptr from static to one with our own reference
let c3: &WrappedCaller = self.caller.as_ref(); let c3: &WrappedCaller = self.caller.as_ref();
self.frames.cur_frame_mut().head_node = unsafe { self.call_user_component(c3) }; match c3(self) {
None => {
Ok(()) // the user's component failed. We avoid cycling to the next frame
} log::error!("Running your component failed! It will no longer receive events.");
Err(Error::ComponentFailed)
// this is its own function so we can preciesly control how lifetimes flow }
unsafe fn call_user_component<'a>(&'a self, caller: &WrappedCaller) -> VNode<'static> { Some(new_head) => {
let new_head: Option<VNode<'a>> = caller(self); // the user's component succeeded. We can safely cycle to the next frame
let new_head = new_head.unwrap_or(errored_fragment()); self.frames.old_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
std::mem::transmute(new_head) self.frames.cycle_frame();
Ok(())
}
}
} }
// A safe wrapper around calling listeners // A safe wrapper around calling listeners
+13 -10
View File
@@ -30,7 +30,9 @@ use std::any::TypeId;
use std::cell::RefCell; use std::cell::RefCell;
use std::pin::Pin; use std::pin::Pin;
pub type ScopeId = DefaultKey; slotmap::new_key_type! {
pub struct ScopeId;
}
/// An integrated virtual node system that progresses events and diffs UI trees. /// An integrated virtual node system that progresses events and diffs UI trees.
/// Differences are converted into patches which a renderer can use to draw the UI. /// Differences are converted into patches which a renderer can use to draw the UI.
@@ -140,7 +142,7 @@ impl VirtualDom {
/// let dom = VirtualDom::new(Example); /// let dom = VirtualDom::new(Example);
/// ``` /// ```
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self { pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
let components = SharedArena::new(SlotMap::new()); let components = SharedArena::new(SlotMap::<ScopeId, Scope>::with_key());
let root_props: Pin<Box<dyn Any>> = Box::pin(root_props); let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P; let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
@@ -225,11 +227,12 @@ impl VirtualDom {
); );
let cur_component = self.components.get_mut(self.base_scope).unwrap(); let cur_component = self.components.get_mut(self.base_scope).unwrap();
cur_component.run_scope()?;
let meta = diff_machine.create(cur_component.next_frame()); // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
if cur_component.run_scope().is_ok() {
diff_machine.edits.append_children(meta.added_to_stack); let meta = diff_machine.create(cur_component.next_frame());
diff_machine.edits.append_children(meta.added_to_stack);
}
Ok(()) Ok(())
} }
@@ -370,10 +373,10 @@ impl VirtualDom {
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified // We are guaranteeed that this scope is unique because we are tracking which nodes have modified
let cur_component = self.components.get_mut(update.idx).unwrap(); let cur_component = self.components.get_mut(update.idx).unwrap();
cur_component.run_scope()?; if cur_component.run_scope().is_ok() {
let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
let (old, new) = (cur_component.old_frame(), cur_component.next_frame()); diff_machine.diff_node(old, new);
diff_machine.diff_node(old, new); }
} }
} }
} }
+1 -1
View File
@@ -106,7 +106,7 @@ impl<'a, T: 'static> UseState<'a, T> {
*self.inner.wip.borrow_mut() = Some(new_val); *self.inner.wip.borrow_mut() = Some(new_val);
} }
pub fn get(&self) -> &T { pub fn get(&self) -> &'a T {
&self.inner.current_val &self.inner.current_val
} }
+1 -2
View File
@@ -21,8 +21,7 @@ pretty_env_logger = "0.4.0"
console_error_panic_hook = "0.1.6" console_error_panic_hook = "0.1.6"
generational-arena = "0.2.8" generational-arena = "0.2.8"
wasm-bindgen-test = "0.3.21" wasm-bindgen-test = "0.3.21"
once_cell = "1.7.2" once_cell = "1.8"
# atoms = { path = "../atoms" }
async-channel = "1.6.1" async-channel = "1.6.1"
anyhow = "1.0.41" anyhow = "1.0.41"
slotmap = "1.0.3" slotmap = "1.0.3"