wip: more progress on parity docs.

Placeholders in the rsx macro for future features.
This commit is contained in:
Jonathan Kelley committed 2021-05-28 12:56:21 -04:00
1 parent 4d5c528b07
commit c5089ba3c5
12 files changed
+147 -241

No files matched your search

-123
View File
@@ -1,123 +0,0 @@
use crate::{innerlude::*, nodebuilder::IntoDomTree};
use crate::{nodebuilder::LazyNodes, nodes::VNode};
use bumpalo::Bump;
use std::{cell::RefCell, future::Future, ops::Deref, pin::Pin, rc::Rc, sync::atomic::AtomicUsize};
/// Context API
///
/// The context API provides a mechanism for components to borrow state from other components higher in the tree.
/// By combining the Context API and the Subscription API, we can craft ergonomic global state management systems.
///
/// This API is inherently dangerous because we could easily cause UB by allowing &T and &mut T to exist at the same time.
/// To prevent this, we expose the RemoteState<T> and RemoteLock<T> types which act as a form of reverse borrowing.
/// This is very similar to RwLock, except that RemoteState is copy-able. Unlike RwLock, derefing RemoteState can
/// cause panics if the pointer is null. In essence, we sacrifice the panic protection for ergonomics, but arrive at
/// a similar end result.
///
/// Instead of placing the onus on the receiver of the data to use it properly, we wrap the source object in a
/// "shield" where gaining &mut access can only be done if no active StateGuards are open. This would fail and indicate
/// a failure of implementation.
pub struct RemoteState<T> {
inner: *const T,
}
impl<T> Copy for RemoteState<T> {}
impl<T> Clone for RemoteState<T> {
fn clone(&self) -> Self {
Self { inner: self.inner }
}
}
static DEREF_ERR_MSG: &'static str = r#"""
[ERROR]
This state management implementation is faulty. Report an issue on whatever implementation is using this.
Context should *never* be dangling!. If a Context is torn down, so should anything that references it.
"""#;
impl<T> Deref for RemoteState<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
// todo!
// Try to borrow the underlying context manager, register this borrow with the manager as a "weak" subscriber.
// This will prevent the panic and ensure the pointer still exists.
// For now, just get an immutable reference to the underlying context data.
//
// It's important to note that ContextGuard is not a public API, and can only be made from UseContext.
// This guard should only be used in components, and never stored in hooks
unsafe {
match self.inner.as_ref() {
Some(ptr) => ptr,
None => panic!(DEREF_ERR_MSG),
}
}
}
}
// impl<'a> crate::virtual_dom::Context<'a> {
// // impl<'a, P> super::Context<'a, P> {
// pub fn use_context<I, O>(&'a self, _narrow: impl Fn(&'_ I) -> &'_ O) -> RemoteState<O> {
// todo!()
// }
// pub fn create_context<T: 'static>(&self, creator: impl FnOnce() -> T) {}
// }
/// # SAFETY ALERT
///
/// The underlying context mechanism relies on mutating &mut T while &T is held by components in the tree.
/// By definition, this is UB. Therefore, implementing use_context should be done with upmost care to invalidate and
/// prevent any code where &T is still being held after &mut T has been taken and T has been mutated.
///
/// While mutating &mut T while &T is captured by listeners, we can do any of:
/// 1) Prevent those listeners from being called and avoid "producing" UB values
/// 2) Delete instances of closures where &T is captured before &mut T is taken
/// 3) Make clones of T to preserve the original &T.
/// 4) Disable any &T remotely (like RwLock, RefCell, etc)
///
/// To guarantee safe usage of state management solutions, we provide Dioxus-Reducer and Dioxus-Dataflow built on the
/// SafeContext API. This should provide as an example of how to implement context safely for 3rd party state management.
///
/// It's important to recognize that while safety is a top concern for Dioxus, ergonomics do take prescendence.
/// Contrasting with the JS ecosystem, Rust is faster, but actually "less safe". JS is, by default, a "safe" language.
/// However, it does not protect you against data races: the primary concern for 3rd party implementers of Context.
///
/// We guarantee that any &T will remain consistent throughout the life of the Virtual Dom and that
/// &T is owned by components owned by the VirtualDom. Therefore, it is impossible for &T to:
/// - be dangling or unaligned
/// - produce an invalid value
/// - produce uninitialized memory
///
/// The only UB that is left to the implementer to prevent are Data Races.
///
/// Here's a strategy that is UB:
/// 1. &T is handed out via use_context
/// 2. an event is reduced against the state
/// 3. An &mut T is taken
/// 4. &mut T is mutated.
///
/// Now, any closures that caputed &T are subject to a data race where they might have skipped checks and UB
/// *will* affect the program.
///
/// Here's a strategy that's not UB (implemented by SafeContext):
/// 1. ContextGuard<T> is handed out via use_context.
/// 2. An event is reduced against the state.
/// 3. The state is cloned.
/// 4. All subfield selectors are evaluated and then diffed with the original.
/// 5. Fields that have changed have their ContextGuard poisoned, revoking their ability to take &T.a.
/// 6. The affected fields of Context are mutated.
/// 7. Scopes with poisoned guards are regenerated so they can take &T.a again, calling their lifecycle.
///
/// In essence, we've built a "partial borrowing" mechanism for Context objects.
///
/// =================
/// nb
/// =================
/// If you want to build a state management API directly and deal with all the unsafe and UB, we provide
/// `use_context_unchecked` with all the stability with *no* guarantess of Data Race protection. You're on
/// your own to not affect user applications.
///
/// - Dioxus reducer is built on the safe API and provides a useful but slightly limited API.
/// - Dioxus Dataflow is built on the unsafe API and provides an even snazzier API than Dioxus Reducer.
fn _blah() {}
+1 -3
View File
@@ -41,11 +41,9 @@ use std::{
cell::{RefCell, RefMut},
cmp::Ordering,
collections::VecDeque,
rc::{Rc, Weak},
sync::atomic::AtomicU32,
// rc::{Rc, Weak},
sync::{Arc, Weak},
};
type Rc<T> = Arc<T>;
/// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while
/// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event.
+12 -12
View File
@@ -6,7 +6,7 @@
//! </div>
//! Dioxus: a concurrent, functional, reactive virtual dom for any renderer in Rust.
//!
//! This crate aims to maintain a uniform hook-based, renderer-agnostic UI framework for cross-platform development.
//! This crate aims to maintain a hook-based, renderer-agnostic framework for cross-platform UI development.
//!
//! ## Components
//! The base unit of Dioxus is the `component`. Components can be easily created from just a function - no traits required:
@@ -16,8 +16,8 @@
//! #[derive(Properties)]
//! struct Props { name: String }
//!
//! fn Example(ctx: &mut Context<Props>) -> VNode {
//! html! { <div> "Hello {ctx.props.name}!" </div> }
//! fn Example(ctx: Context, props: &Props) -> DomTree {
//! html! { <div> "Hello {props.name}!" </div> }
//! }
//! ```
//! Components need to take a "Context" parameter which is generic over some properties. This defines how the component can be used
@@ -37,11 +37,14 @@
//! ```
//!
//! If the properties struct is too noisy for you, we also provide a macro that converts variadic functions into components automatically.
//! Many people don't like the magic of proc macros, so this is entirely optional. Under-the-hood, we simply transplant the
//! function arguments into a struct, so there's very little actual magic happening.
//!
//! ```
//! use dioxus_core::prelude::*;
//!
//! #[fc]
//! static Example: FC = |ctx, name: String| {
//! #[derive_props]
//! static Example: FC = |ctx, name: &String| {
//! html! { <div> "Hello {name}!" </div> }
//! }
//! ```
@@ -49,7 +52,8 @@
//! ## Hooks
//! Dioxus uses hooks for state management. Hooks are a form of state persisted between calls of the function component. Instead of
//! using a single struct to store data, hooks use the "use_hook" building block which allows the persistence of data between
//! function component renders.
//! function component renders. Each hook stores some data in a "memory cell" and needs to be called in a consistent order.
//! This means hooks "anything with `use_x`" may not be called conditionally.
//!
//! This allows functions to reuse stateful logic between components, simplify large complex components, and adopt more clear context
//! subscription patterns to make components easier to read.
@@ -67,7 +71,7 @@
pub mod arena;
pub mod component; // Logic for extending FC
pub mod context; // Logic for providing hook + context functionality to user components
pub mod debug_renderer;
pub mod diff;
pub mod patch; // An "edit phase" described by transitions and edit operations // Test harness for validating that lifecycles and diffs work appropriately
@@ -86,7 +90,7 @@ pub mod builder {
// types used internally that are important
pub(crate) mod innerlude {
pub use crate::component::*;
pub use crate::context::*;
pub use crate::debug_renderer::*;
pub use crate::diff::*;
pub use crate::error::*;
@@ -99,10 +103,6 @@ pub(crate) mod innerlude {
pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
// TODO @Jon, fix this
// hack the VNode type until VirtualNode is fixed in the macro crate
pub type VirtualNode<'a> = VNode<'a>;
// Re-export the FC macro
pub use crate as dioxus;
pub use crate::nodebuilder as builder;
+3 -46
View File
@@ -5,14 +5,10 @@
use crate::{
events::VirtualEvent,
innerlude::{Context, NodeCtx, Properties, ScopeIdx, FC},
innerlude::{Context, Properties, ScopeIdx, FC},
};
use bumpalo::Bump;
use std::fmt::Debug;
use std::sync::Arc;
use std::{any::Any, cell::RefCell, marker::PhantomData};
type Rc<T> = Arc<T>;
use std::{cell::RefCell, fmt::Debug, marker::PhantomData, rc::Rc};
/// A domtree represents the result of "Viewing" the context
/// It's a placeholder over vnodes, to make working with lifetimes easier
@@ -21,25 +17,16 @@ pub struct DomTree {
pub(crate) root: VNode<'static>,
}
// ==============================
// VNODES
// ==============================
/// Tools for the base unit of the virtual dom - the VNode
/// VNodes are intended to be quickly-allocated, lightweight enum values.
///
/// Components will be generating a lot of these very quickly, so we want to
/// limit the amount of heap allocations / overly large enum sizes.
#[derive(Debug)]
pub enum VNode<'src> {
/// An element node (node type `ELEMENT_NODE`).
Element(&'src VElement<'src>),
/// A text node (node type `TEXT_NODE`).
///
/// Note: This wraps a `VText` instead of a plain `String` in
/// order to enable custom methods like `create_text_node()` on the
/// wrapped type.
Text(VText<'src>),
/// A "suspended component"
@@ -101,7 +88,6 @@ impl<'a> VNode<'a> {
// ========================================================
// VElement (div, h1, etc), attrs, keys, listener handle
// ========================================================
#[derive(Debug)]
pub struct VElement<'a> {
/// Elements have a tag name, zero or more attributes, and zero or more
pub key: NodeKey<'a>,
@@ -112,20 +98,6 @@ pub struct VElement<'a> {
pub namespace: Option<&'a str>,
}
impl<'a> VElement<'a> {
// The tag of a component MUST be known at compile time
pub fn new(_tag: &'a str) -> Self {
todo!()
// VElement {
// tag,
// attrs: HashMap::new(),
// events: HashMap::new(),
// // events: Events(HashMap::new()),
// children: vec![],
// }
}
}
/// An attribute on a DOM node, such as `id="my-thing"` or
/// `href="https://example.com"`.
#[derive(Clone, Debug)]
@@ -174,18 +146,9 @@ pub struct Listener<'bump> {
pub scope: ScopeIdx,
pub id: usize,
// pub(crate) _i: &'bump str,
// #[serde(skip_serializing, skip_deserializing, default="")]
// /// The callback to invoke when the event happens.
/// The callback to invoke when the event happens.
pub(crate) callback: &'bump (dyn Fn(VirtualEvent)),
}
impl Debug for Listener<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Listener")
.field("event", &self.event)
.finish()
}
}
/// The key for keyed children.
///
@@ -263,12 +226,6 @@ pub struct VComponent<'src> {
_p: PhantomData<&'src ()>,
}
impl std::fmt::Debug for VComponent<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
impl<'a> VComponent<'a> {
// use the type parameter on props creation and move it into a portable context
// this lets us keep scope generic *and* downcast its props when we need to:
+6 -8
View File
@@ -29,12 +29,9 @@ use std::{
fmt::Debug,
future::Future,
pin::Pin,
// rc::{Rc, Weak},
sync::{Arc, Weak},
rc::{Rc, Weak},
};
type Rc<T> = Arc<T>;
/// 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.
pub struct VirtualDom {
@@ -1100,15 +1097,16 @@ mod tests {
// ensure the virtualdom is send + sync
// needed for use in async/await contexts
#[test]
fn is_send_sync() {
fn check_send<T: Send>(a: T) -> T {
fn check_send<T: Send>(_a: T) -> T {
todo!()
}
fn check_sync<T: Sync>(a: T) -> T {
fn check_sync<T: Sync>(_a: T) -> T {
todo!()
}
let _ = check_send(VirtualDom::new(|ctx, props| ctx.render(rsx! { div {}})));
let _ = check_sync(VirtualDom::new(|ctx, props| ctx.render(rsx! { div {}})));
let _ = check_send(VirtualDom::new(|ctx, _props| ctx.render(rsx! { div {}})));
let _ = check_sync(VirtualDom::new(|ctx, _props| ctx.render(rsx! { div {}})));
}
}