chore: wipe away old src

This commit is contained in:
Jonathan Kelley committed 2022-11-20 15:58:05 -08:00
1 parent 493591400f
commit d407035931
29 files changed
+187 -6819

No files matched your search

-237
View File
@@ -1,237 +0,0 @@
use std::{any::Any, fmt::Formatter};
/// Possible values for an attribute
// trying to keep values at 3 bytes
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", serde(untagged))]
#[derive(Clone, Debug, PartialEq)]
#[allow(missing_docs)]
pub enum AttributeValue<'a> {
Text(&'a str),
Float32(f32),
Float64(f64),
Int32(i32),
Int64(i64),
Uint32(u32),
Uint64(u64),
Bool(bool),
Vec3Float(f32, f32, f32),
Vec3Int(i32, i32, i32),
Vec3Uint(u32, u32, u32),
Vec4Float(f32, f32, f32, f32),
Vec4Int(i32, i32, i32, i32),
Vec4Uint(u32, u32, u32, u32),
Bytes(&'a [u8]),
// Any(ArbitraryAttributeValue<'a>),
}
// todo
#[allow(missing_docs)]
impl<'a> AttributeValue<'a> {
pub fn is_truthy(&self) -> bool {
match self {
AttributeValue::Text(t) => *t == "true",
AttributeValue::Bool(t) => *t,
_ => false,
}
}
pub fn is_falsy(&self) -> bool {
match self {
AttributeValue::Text(t) => *t == "false",
AttributeValue::Bool(t) => !(*t),
_ => false,
}
}
}
impl<'a> std::fmt::Display for AttributeValue<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
AttributeValue::Text(a) => write!(f, "{}", a),
AttributeValue::Float32(a) => write!(f, "{}", a),
AttributeValue::Float64(a) => write!(f, "{}", a),
AttributeValue::Int32(a) => write!(f, "{}", a),
AttributeValue::Int64(a) => write!(f, "{}", a),
AttributeValue::Uint32(a) => write!(f, "{}", a),
AttributeValue::Uint64(a) => write!(f, "{}", a),
AttributeValue::Bool(a) => write!(f, "{}", a),
AttributeValue::Vec3Float(_, _, _) => todo!(),
AttributeValue::Vec3Int(_, _, _) => todo!(),
AttributeValue::Vec3Uint(_, _, _) => todo!(),
AttributeValue::Vec4Float(_, _, _, _) => todo!(),
AttributeValue::Vec4Int(_, _, _, _) => todo!(),
AttributeValue::Vec4Uint(_, _, _, _) => todo!(),
AttributeValue::Bytes(a) => write!(f, "{:?}", a),
// AttributeValue::Any(a) => write!(f, "{:?}", a),
}
}
}
#[derive(Clone, Copy)]
#[allow(missing_docs)]
pub struct ArbitraryAttributeValue<'a> {
pub value: &'a dyn Any,
pub cmp: fn(&'a dyn std::any::Any, &'a dyn std::any::Any) -> bool,
}
// pub trait AnyAttrValue {}
// impl PartialEq for ArbitraryAttributeValue<'_> {
// fn eq(&self, other: &Self) -> bool {
// (self.cmp)(self.value, other.value)
// }
// }
impl std::fmt::Debug for ArbitraryAttributeValue<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ArbitraryAttributeValue").finish()
}
}
#[cfg(feature = "serialize")]
impl<'a> serde::Serialize for ArbitraryAttributeValue<'a> {
fn serialize<S>(&self, _serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
panic!("ArbitraryAttributeValue should not be serialized")
}
}
#[cfg(feature = "serialize")]
impl<'de, 'a> serde::Deserialize<'de> for &'a ArbitraryAttributeValue<'a> {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
panic!("ArbitraryAttributeValue is not deserializable!")
}
}
#[cfg(feature = "serialize")]
impl<'de, 'a> serde::Deserialize<'de> for ArbitraryAttributeValue<'a> {
fn deserialize<D>(_deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
panic!("ArbitraryAttributeValue is not deserializable!")
}
}
// todo
#[allow(missing_docs)]
impl<'a> AttributeValue<'a> {
pub fn as_text(&self) -> Option<&'a str> {
match self {
AttributeValue::Text(s) => Some(s),
_ => None,
}
}
pub fn as_float32(&self) -> Option<f32> {
match self {
AttributeValue::Float32(f) => Some(*f),
_ => None,
}
}
pub fn as_float64(&self) -> Option<f64> {
match self {
AttributeValue::Float64(f) => Some(*f),
_ => None,
}
}
pub fn as_int32(&self) -> Option<i32> {
match self {
AttributeValue::Int32(i) => Some(*i),
_ => None,
}
}
pub fn as_int64(&self) -> Option<i64> {
match self {
AttributeValue::Int64(i) => Some(*i),
_ => None,
}
}
pub fn as_uint32(&self) -> Option<u32> {
match self {
AttributeValue::Uint32(i) => Some(*i),
_ => None,
}
}
pub fn as_uint64(&self) -> Option<u64> {
match self {
AttributeValue::Uint64(i) => Some(*i),
_ => None,
}
}
pub fn as_bool(&self) -> Option<bool> {
match self {
AttributeValue::Bool(b) => Some(*b),
_ => None,
}
}
pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> {
match self {
AttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)),
_ => None,
}
}
pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> {
match self {
AttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)),
_ => None,
}
}
pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> {
match self {
AttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)),
_ => None,
}
}
pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> {
match self {
AttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)),
_ => None,
}
}
pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> {
match self {
AttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)),
_ => None,
}
}
pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> {
match self {
AttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)),
_ => None,
}
}
pub fn as_bytes(&self) -> Option<&[u8]> {
match self {
AttributeValue::Bytes(b) => Some(b),
_ => None,
}
}
// pub fn as_any(&self) -> Option<&'a ArbitraryAttributeValue> {
// match self {
// AttributeValue::Any(a) => Some(a),
// _ => None,
// }
// }
}
File diff suppressed because it is too large. Load diff
-182
View File
@@ -1,182 +0,0 @@
//! Internal and external event system
//!
//!
//! This is all kinda WIP, but the bones are there.
use crate::{ElementId, ScopeId};
use std::{any::Any, cell::Cell, fmt::Debug, rc::Rc, sync::Arc};
pub(crate) struct BubbleState {
pub canceled: Cell<bool>,
}
impl BubbleState {
pub fn new() -> Self {
Self {
canceled: Cell::new(false),
}
}
}
/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
///
/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
/// where each listener is checked and fired if the event name matches.
///
/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
/// attempting to downcast the event data.
///
/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
///
/// # Example
/// ```rust, ignore
/// fn App(cx: Scope) -> Element {
/// render!(div {
/// onclick: move |_| println!("Clicked!")
/// })
/// }
///
/// let mut dom = VirtualDom::new(App);
/// let mut scheduler = dom.get_scheduler_channel();
/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
/// UserEvent {
/// scope_id: None,
/// priority: EventPriority::Medium,
/// name: "click",
/// element: Some(ElementId(0)),
/// data: Arc::new(ClickEvent { .. })
/// }
/// )).unwrap();
/// ```
#[derive(Debug, Clone)]
pub struct UserEvent {
/// The originator of the event trigger if available
pub scope_id: Option<ScopeId>,
/// The priority of the event to be scheduled around ongoing work
pub priority: EventPriority,
/// The optional real node associated with the trigger
pub element: Option<ElementId>,
/// The event type IE "onclick" or "onmouseover"
pub name: &'static str,
/// If the event is bubbles up through the vdom
pub bubbles: bool,
/// The event data to be passed onto the event handler
pub data: Arc<dyn Any + Send + Sync>,
}
/// Priority of Event Triggers.
///
/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus
/// won't be afraid to pause work or flush changes to the Real Dom. This is called "cooperative scheduling". Some Renderers
/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
///
/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
///
/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now,
/// we keep it simple, and just use a 3-tier priority system.
///
/// - `NoPriority` = 0
/// - `LowPriority` = 1
/// - `NormalPriority` = 2
/// - `UserBlocking` = 3
/// - `HighPriority` = 4
/// - `ImmediatePriority` = 5
///
/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will.
/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be
/// flushed before proceeding. Multiple discrete events is highly unlikely, though.
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
pub enum EventPriority {
/// Work that must be completed during the EventHandler phase.
///
/// Currently this is reserved for controlled inputs.
Immediate = 3,
/// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work.
///
/// This is typically reserved for things like user interaction.
///
/// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate).
High = 2,
/// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important
/// than "High Priority" events and will take precedence over low priority events.
///
/// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
///
/// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc).
Medium = 1,
/// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be
/// advanced to the front of the work queue until completed.
///
/// The primary user of Low Priority work is the asynchronous work system (Suspense).
///
/// This is considered "idle" work or "background" work.
Low = 0,
}
/// The internal Dioxus type that carries any event data to the relevant handler.
pub struct AnyEvent {
pub(crate) bubble_state: Rc<BubbleState>,
pub(crate) data: Arc<dyn Any + Send + Sync>,
}
impl AnyEvent {
/// Convert this [`AnyEvent`] into a specific [`UiEvent`] with [`EventData`].
///
/// ```rust, ignore
/// let evt: FormEvent = evvt.downcast().unwrap();
/// ```
#[must_use]
pub fn downcast<T: Send + Sync + 'static>(self) -> Option<UiEvent<T>> {
let AnyEvent { data, bubble_state } = self;
data.downcast::<T>()
.ok()
.map(|data| UiEvent { data, bubble_state })
}
}
/// A [`UiEvent`] is a type that wraps various [`EventData`].
///
/// You should prefer to use the name of the event directly, rather than
/// the [`UiEvent`]<T> generic type.
///
/// For the HTML crate, this would include `MouseEvent`, `FormEvent` etc.
pub struct UiEvent<T> {
/// The internal data of the event
/// This is wrapped in an Arc so that it can be sent across threads
pub data: Arc<T>,
#[allow(unused)]
bubble_state: Rc<BubbleState>,
}
impl<T: Debug> std::fmt::Debug for UiEvent<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("UiEvent").field("data", &self.data).finish()
}
}
impl<T> std::ops::Deref for UiEvent<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
self.data.as_ref()
}
}
impl<T> UiEvent<T> {
/// Prevent this event from bubbling up the tree.
pub fn cancel_bubble(&self) {
self.bubble_state.canceled.set(true);
}
}
-322
View File
@@ -1,322 +0,0 @@
//! Support for storing lazy-nodes on the stack
//!
//! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls
//! to `rsx!` more efficient.
//!
//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`NodeFactory`] closures.
//!
//! 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
//! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
use crate::innerlude::{NodeFactory, VNode};
use std::mem;
/// A concrete type provider for closures that build [`VNode`] structures.
///
/// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over
/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of [`IntoVNode`].
///
///
/// ```rust, ignore
/// LazyNodes::new(|f| f.element("div", [], [], [] None))
/// ```
pub struct LazyNodes<'a, 'b> {
inner: StackNodeStorage<'a, 'b>,
}
type StackHeapSize = [usize; 16];
enum StackNodeStorage<'a, 'b> {
Stack(LazyStack),
Heap(Box<dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b>),
}
impl<'a, 'b> LazyNodes<'a, 'b> {
/// Create a new [`LazyNodes`] closure, optimistically placing it onto the stack.
///
/// If the closure cannot fit into the stack allocation (16 bytes), then it
/// is placed on the heap. Most closures will fit into the stack, and is
/// the most optimal way to use the creation function.
pub fn new(val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self {
// there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
let mut slot = Some(val);
let val = move |fac: Option<NodeFactory<'a>>| {
fac.map(
slot.take()
.expect("LazyNodes closure to be called only once"),
)
};
// miri does not know how to work with mucking directly into bytes
// just use a heap allocated type when miri is running
if cfg!(miri) {
Self {
inner: StackNodeStorage::Heap(Box::new(val)),
}
} else {
unsafe { LazyNodes::new_inner(val) }
}
}
/// Create a new [`LazyNodes`] closure, but force it onto the heap.
pub fn new_boxed<F>(inner: F) -> Self
where
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
{
// there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
let mut slot = Some(inner);
Self {
inner: StackNodeStorage::Heap(Box::new(move |fac: Option<NodeFactory<'a>>| {
fac.map(
slot.take()
.expect("LazyNodes closure to be called only once"),
)
})),
}
}
unsafe fn new_inner<F>(val: F) -> Self
where
F: FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b,
{
let mut ptr: *const _ = &val as &dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>;
assert_eq!(
ptr as *const u8, &val as *const _ as *const u8,
"MISUSE: Closure returned different pointer"
);
assert_eq!(
std::mem::size_of_val(&*ptr),
std::mem::size_of::<F>(),
"MISUSE: Closure returned a subset pointer"
);
let words = ptr_as_slice(&mut ptr);
assert!(
words[0] == &val as *const _ as usize,
"BUG: Pointer layout is not (data_ptr, info...)"
);
// - Ensure that Self is aligned same as data requires
assert!(
std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
"TODO: Enforce alignment >{} (requires {})",
std::mem::align_of::<Self>(),
std::mem::align_of::<F>()
);
let info = &words[1..];
let data = words[0] as *mut ();
let size = mem::size_of::<F>();
let stored_size = info.len() * mem::size_of::<usize>() + size;
let max_size = mem::size_of::<StackHeapSize>();
if stored_size > max_size {
Self {
inner: StackNodeStorage::Heap(Box::new(val)),
}
} else {
let mut buf: StackHeapSize = StackHeapSize::default();
assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
// Place pointer information at the end of the region
// - Allows the data to be at the start for alignment purposes
{
let info_ofs = buf.as_ref().len() - info.len();
let info_dst = &mut buf.as_mut()[info_ofs..];
for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
*d = *v;
}
}
let src_ptr = data as *const u8;
let dataptr = buf.as_mut_ptr().cast::<u8>();
for i in 0..size {
*dataptr.add(i) = *src_ptr.add(i);
}
std::mem::forget(val);
Self {
inner: StackNodeStorage::Stack(LazyStack {
_align: [],
buf,
dropped: false,
}),
}
}
}
/// Call the closure with the given factory to produce real [`VNode`].
///
/// ```rust, ignore
/// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None));
///
/// let fac = NodeFactory::new(&cx);
///
/// let node = f.call(cac);
/// ```
#[must_use]
pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> {
match self.inner {
StackNodeStorage::Heap(mut lazy) => {
lazy(Some(f)).expect("Closure should not be called twice")
}
StackNodeStorage::Stack(mut stack) => stack.call(f),
}
}
}
struct LazyStack {
_align: [u64; 0],
buf: StackHeapSize,
dropped: bool,
}
impl LazyStack {
fn call<'a>(&mut self, f: NodeFactory<'a>) -> VNode<'a> {
let LazyStack { buf, .. } = self;
let data = buf.as_ref();
let info_size =
mem::size_of::<*mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>>>()
/ mem::size_of::<usize>()
- 1;
let info_ofs = data.len() - info_size;
let g: *mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> =
unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
self.dropped = true;
let clos = unsafe { &mut *g };
clos(Some(f)).unwrap()
}
}
impl Drop for LazyStack {
fn drop(&mut self) {
if !self.dropped {
let LazyStack { buf, .. } = self;
let data = buf.as_ref();
let info_size = mem::size_of::<
*mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>>,
>() / mem::size_of::<usize>()
- 1;
let info_ofs = data.len() - info_size;
let g: *mut dyn FnMut(Option<NodeFactory<'_>>) -> Option<VNode<'_>> =
unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
self.dropped = true;
let clos = unsafe { &mut *g };
clos(None);
}
}
}
/// Obtain mutable access to a pointer's words
fn ptr_as_slice<T>(ptr: &mut T) -> &mut [usize] {
assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0);
let words = mem::size_of::<T>() / mem::size_of::<usize>();
// SAFE: Points to valid memory (a raw pointer)
unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) }
}
/// Re-construct a fat pointer
unsafe fn make_fat_ptr<T: ?Sized>(data_ptr: usize, meta_vals: &[usize]) -> *mut T {
let mut rv = mem::MaybeUninit::<*mut T>::uninit();
{
let s = ptr_as_slice(&mut rv);
s[0] = data_ptr;
s[1..].copy_from_slice(meta_vals);
}
let rv = rv.assume_init();
assert_eq!(rv as *const (), data_ptr as *const ());
rv
}
fn round_to_words(len: usize) -> usize {
(len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
}
// #[cfg(test)]
// mod tests {
// use super::*;
// use crate::innerlude::{Element, Scope, VirtualDom};
// #[test]
// fn it_works() {
// fn app(cx: Scope<()>) -> Element {
// cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!"))))
// }
// let mut dom = VirtualDom::new(app);
// dom.rebuild();
// let g = dom.base_scope().root_node();
// dbg!(g);
// }
// #[test]
// fn it_drops() {
// use std::rc::Rc;
// struct AppProps {
// inner: Rc<i32>,
// }
// fn app(cx: Scope<AppProps>) -> Element {
// struct DropInner {
// id: i32,
// }
// impl Drop for DropInner {
// fn drop(&mut self) {
// eprintln!("dropping inner");
// }
// }
// let caller = {
// let it = (0..10).map(|i| {
// let val = cx.props.inner.clone();
// LazyNodes::new(move |f| {
// eprintln!("hell closure");
// let inner = DropInner { id: i };
// f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
// })
// });
// LazyNodes::new(|f| {
// eprintln!("main closure");
// f.fragment_from_iter(it)
// })
// };
// cx.render(caller)
// }
// let inner = Rc::new(0);
// let mut dom = VirtualDom::new_with_props(
// app,
// AppProps {
// inner: inner.clone(),
// },
// );
// dom.rebuild();
// drop(dom);
// assert_eq!(Rc::strong_count(&inner), 1);
// }
// }
-131
View File
@@ -1,131 +0,0 @@
#![allow(non_snake_case)]
#![doc = include_str!("../README.md")]
#![warn(missing_docs)]
pub(crate) mod diff;
pub(crate) mod events;
pub(crate) mod lazynodes;
pub(crate) mod mutations;
pub(crate) mod nodes;
pub(crate) mod properties;
pub(crate) mod scopes;
pub(crate) mod virtual_dom;
pub(crate) mod innerlude {
pub use crate::events::*;
pub use crate::lazynodes::*;
pub use crate::mutations::*;
pub use crate::nodes::*;
pub use crate::properties::*;
pub use crate::scopes::*;
pub use crate::virtual_dom::*;
/// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
///
/// Any [`None`] [`Element`] will automatically be coerced into a placeholder [`VNode`] with the [`VNode::Placeholder`] variant.
pub type Element<'a> = Option<VNode<'a>>;
/// A [`Component`] is a function that takes a [`Scope`] and returns an [`Element`].
///
/// Components can be used in other components with two syntax options:
/// - lowercase as a function call with named arguments (rust style)
/// - uppercase as an element (react style)
///
/// ## Rust-Style
///
/// ```rust, ignore
/// fn example(cx: Scope<Props>) -> Element {
/// // ...
/// }
///
/// rsx!(
/// example()
/// )
/// ```
/// ## React-Style
/// ```rust, ignore
/// fn Example(cx: Scope<Props>) -> Element {
/// // ...
/// }
///
/// rsx!(
/// Example {}
/// )
/// ```
pub type Component<P = ()> = fn(Scope<P>) -> Element;
/// A list of attributes
pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
}
pub use crate::innerlude::{
AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, NodeFactory, Properties, Renderer,
SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
UiEvent, UserEvent, VComponent, VElement, VNode, VTemplate, VText, VirtualDom,
};
/// The purpose of this module is to alleviate imports of many common types
///
/// This includes types like [`Scope`], [`Element`], and [`Component`].
pub mod prelude {
pub use crate::innerlude::{
fc_to_builder, Attributes, Component, Element, EventHandler, LazyNodes, NodeFactory,
Properties, Scope, ScopeId, ScopeState, Template, VNode, VirtualDom,
};
}
pub mod exports {
//! Important dependencies that are used by the rest of the library
//! Feel free to just add the dependencies in your own Crates.toml
pub use bumpalo;
pub use futures_channel;
}
/// Functions that wrap unsafe functionality to prevent us from misusing it at the callsite
pub(crate) mod unsafe_utils {
use crate::VNode;
pub(crate) unsafe fn extend_vnode<'a, 'b>(node: &'a VNode<'a>) -> &'b VNode<'b> {
std::mem::transmute(node)
}
}
#[macro_export]
/// A helper macro for using hooks in async environements.
///
/// # Usage
///
///
/// ```ignore
/// let (data) = use_ref(&cx, || {});
///
/// let handle_thing = move |_| {
/// to_owned![data]
/// cx.spawn(async move {
/// // do stuff
/// });
/// }
/// ```
macro_rules! to_owned {
($($es:ident),+) => {$(
#[allow(unused_mut)]
let mut $es = $es.to_owned();
)*}
}
/// get the code location of the code that called this function
#[macro_export]
macro_rules! get_line_num {
() => {
concat!(
file!(),
":",
line!(),
":",
column!(),
":",
env!("CARGO_MANIFEST_DIR")
)
};
}
-507
View File
@@ -1,507 +0,0 @@
//! 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::*;
/// A renderer for Dioxus to modify during diffing
///
/// The renderer should implement a Stack Machine. IE each call to the below methods are modifications to the renderer's
/// internal stack for creating and modifying nodes.
///
/// Dioxus guarantees that the stack is always in a valid state.
pub trait Renderer<'a> {
/// Load this element onto the stack
fn push_root(&mut self, root: ElementId);
/// Pop the topmost element from the stack
fn pop_root(&mut self);
/// Replace the given element with the next m elements on the stack
fn replace_with(&mut self, root: ElementId, m: u32);
/// Insert the next m elements on the stack after the given element
fn insert_after(&mut self, root: ElementId, n: u32);
/// Insert the next m elements on the stack before the given element
fn insert_before(&mut self, root: ElementId, n: u32);
/// Append the next n elements on the stack to the n+1 element on the stack
fn append_children(&mut self, n: u32);
/// Create a new element with the given text and ElementId
fn create_text_node(&mut self, text: &'a str, root: ElementId);
/// Create an element with the given tag name, optional namespace, and ElementId
/// Note that namespaces do not cascade down the tree, so the renderer must handle this if it implements namespaces
fn create_element(&mut self, tag: &'static str, ns: Option<&'static str>, id: ElementId);
/// Create a hidden element to be used later for replacement.
/// Used in suspense, lists, and other places where we need to hide a node before it is ready to be shown.
/// This is up to the renderer to implement, but it should not be visible to the user.
fn create_placeholder(&mut self, id: ElementId);
/// Remove the targeted node from the DOM
fn remove(&mut self, root: ElementId);
/// Remove an attribute from an existing element
fn remove_attribute(&mut self, attribute: &Attribute, root: ElementId);
/// Remove all the children of the given element
fn remove_children(&mut self, root: ElementId);
/// Attach a new listener to the dom
fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId);
/// Remove an existing listener from the dom
fn remove_event_listener(&mut self, event: &'static str, root: ElementId);
/// Set the text content of a node
fn set_text(&mut self, text: &'a str, root: ElementId);
/// Set an attribute on an element
fn set_attribute(
&mut self,
name: &'static str,
value: AttributeValue<'a>,
namespace: Option<&'a str>,
root: ElementId,
);
/// General statistics for doing things that extend outside of the renderer
fn mark_dirty_scope(&mut self, scope: ScopeId);
/// Save the current n nodes to the ID to be loaded later
fn save(&mut self, id: &'static str, num: u32);
/// Loads a set of saved nodes from the ID into a scratch space
fn load(&mut self, id: &'static str, index: u32);
/// Assign the element on the stack's descendent the given ID
fn assign_id(&mut self, descendent: &'static [u8], id: ElementId);
/// Replace the given element of the topmost element with the next m elements on the stack
/// Is essentially a combination of assign_id and replace_with
fn replace_descendant(&mut self, descendent: &'static [u8], m: u32);
}
/*
div {
div {
div {
div {}
}
}
}
push_child(0)
push_child(1)
push_child(3)
push_child(4)
pop
pop
clone_node(0)
set_node(el, [1,2,3,4])
set_attribute("class", "foo")
append_child(1)
*/
//! 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};
/// ## Mutations
///
/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
/// applied the edits.
///
/// Mutations are the only link between the RealDOM and the VirtualDOM.
pub struct Mutations<'a> {
/// The list of edits that need to be applied for the RealDOM to match the VirtualDOM.
pub edits: Vec<DomEdit<'a>>,
/// The list of Scopes that were diffed, created, and removed during the Diff process.
pub dirty_scopes: FxHashSet<ScopeId>,
/// The list of nodes to connect to the RealDOM.
pub refs: Vec<NodeRefMutation<'a>>,
}
impl Debug for Mutations<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Mutations")
.field("edits", &self.edits)
.field("noderefs", &self.refs)
.finish()
}
}
/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the
/// network or through FFI boundaries.
#[derive(Debug, PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type")
)]
pub enum DomEdit<'bump> {
/// Pop the topmost node from our stack and append them to the node
/// at the top of the stack.
AppendChildren {
/// The parent to append nodes to.
root: Option<u64>,
/// The ids of the children to append.
children: Vec<u64>,
},
/// Replace a given (single) node with a handful of nodes currently on the stack.
ReplaceWith {
/// The ID of the node to be replaced.
root: Option<u64>,
/// The ids of the nodes to replace the root with.
nodes: Vec<u64>,
},
/// Insert a number of nodes after a given node.
InsertAfter {
/// The ID of the node to insert after.
root: Option<u64>,
/// The ids of the nodes to insert after the target node.
nodes: Vec<u64>,
},
/// Insert a number of nodes before a given node.
InsertBefore {
/// The ID of the node to insert before.
root: Option<u64>,
/// The ids of the nodes to insert before the target node.
nodes: Vec<u64>,
},
/// Remove a particular node from the DOM
Remove {
/// The ID of the node to remove.
root: Option<u64>,
},
/// Create a new purely-text node
CreateTextNode {
/// The ID the new node should have.
root: Option<u64>,
/// The textcontent of the node
text: &'bump str,
},
/// Create a new purely-element node
CreateElement {
/// The ID the new node should have.
root: Option<u64>,
/// The tagname of the node
tag: &'bump str,
/// The number of children nodes that will follow this message.
children: u32,
},
/// Create a new purely-comment node with a given namespace
CreateElementNs {
/// The ID the new node should have.
root: Option<u64>,
/// The namespace of the node
tag: &'bump str,
/// The namespace of the node (like `SVG`)
ns: &'static str,
/// The number of children nodes that will follow this message.
children: u32,
},
/// Create a new placeholder node.
/// In most implementations, this will either be a hidden div or a comment node.
CreatePlaceholder {
/// The ID the new node should have.
root: Option<u64>,
},
/// Create a new Event Listener.
NewEventListener {
/// The name of the event to listen for.
event_name: &'static str,
/// The ID of the node to attach the listener to.
scope: ScopeId,
/// The ID of the node to attach the listener to.
root: Option<u64>,
},
/// Remove an existing Event Listener.
RemoveEventListener {
/// The ID of the node to remove.
root: Option<u64>,
/// The name of the event to remove.
event: &'static str,
},
/// Set the textcontent of a node.
SetText {
/// The ID of the node to set the textcontent of.
root: Option<u64>,
/// The textcontent of the node
text: &'bump str,
},
/// Set the value of a node's attribute.
SetAttribute {
/// The ID of the node to set the attribute of.
root: Option<u64>,
/// The name of the attribute to set.
field: &'static str,
/// The value of the attribute.
value: AttributeValue<'bump>,
// value: &'bump str,
/// The (optional) namespace of the attribute.
/// For instance, "style" is in the "style" namespace.
ns: Option<&'bump str>,
},
/// Remove an attribute from a node.
RemoveAttribute {
/// The ID of the node to remove.
root: Option<u64>,
/// The name of the attribute to remove.
name: &'static str,
/// The namespace of the attribute.
ns: Option<&'bump str>,
},
/// Clones a node.
CloneNode {
/// The ID of the node to clone.
id: Option<u64>,
/// The ID of the new node.
new_id: u64,
},
/// Clones the children of a node. (allows cloning fragments)
CloneNodeChildren {
/// The ID of the node to clone.
id: Option<u64>,
/// The ID of the new node.
new_ids: Vec<u64>,
},
/// Navigates to the last node to the first child of the current node.
FirstChild {},
/// Navigates to the last node to the last child of the current node.
NextSibling {},
/// Navigates to the last node to the parent of the current node.
ParentNode {},
/// Stores the last node with a new id.
StoreWithId {
/// The ID of the node to store.
id: u64,
},
/// Manually set the last node.
SetLastNode {
/// The ID to set the last node to.
id: u64,
},
}
use rustc_hash::FxHashSet;
use DomEdit::*;
#[allow(unused)]
impl<'a> Mutations<'a> {
pub(crate) fn new() -> Self {
Self {
edits: Vec::new(),
refs: Vec::new(),
dirty_scopes: Default::default(),
}
}
pub(crate) fn replace_with(&mut self, root: Option<u64>, nodes: Vec<u64>) {
self.edits.push(ReplaceWith { nodes, root });
}
pub(crate) fn insert_after(&mut self, root: Option<u64>, nodes: Vec<u64>) {
self.edits.push(InsertAfter { nodes, root });
}
pub(crate) fn insert_before(&mut self, root: Option<u64>, nodes: Vec<u64>) {
self.edits.push(InsertBefore { nodes, root });
}
pub(crate) fn append_children(&mut self, root: Option<u64>, children: Vec<u64>) {
self.edits.push(AppendChildren { root, children });
}
// Remove Nodes from the dom
pub(crate) fn remove(&mut self, id: Option<u64>) {
self.edits.push(Remove { root: id });
}
// Create
pub(crate) fn create_text_node(&mut self, text: &'a str, id: Option<u64>) {
self.edits.push(CreateTextNode { text, root: id });
}
pub(crate) fn create_element(
&mut self,
tag: &'static str,
ns: Option<&'static str>,
id: Option<u64>,
children: u32,
) {
match ns {
Some(ns) => self.edits.push(CreateElementNs {
root: id,
ns,
tag,
children,
}),
None => self.edits.push(CreateElement {
root: id,
tag,
children,
}),
}
}
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
pub(crate) fn create_placeholder(&mut self, id: Option<u64>) {
self.edits.push(CreatePlaceholder { root: id });
}
// events
pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) {
let Listener {
event,
mounted_node,
..
} = listener;
let element_id = Some(mounted_node.get().unwrap().into());
self.edits.push(NewEventListener {
scope,
event_name: event,
root: element_id,
});
}
pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: Option<u64>) {
self.edits.push(RemoveEventListener { event, root });
}
// modify
pub(crate) fn set_text(&mut self, text: &'a str, root: Option<u64>) {
self.edits.push(SetText { text, root });
}
pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: Option<u64>) {
let Attribute {
value, attribute, ..
} = attribute;
self.edits.push(SetAttribute {
field: attribute.name,
value: value.clone(),
ns: attribute.namespace,
root,
});
}
pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: Option<u64>) {
let Attribute { attribute, .. } = attribute;
self.edits.push(RemoveAttribute {
name: attribute.name,
ns: attribute.namespace,
root,
});
}
pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
self.dirty_scopes.insert(scope);
}
pub(crate) fn clone_node(&mut self, id: Option<u64>, new_id: u64) {
self.edits.push(CloneNode { id, new_id });
}
pub(crate) fn clone_node_children(&mut self, id: Option<u64>, new_ids: Vec<u64>) {
self.edits.push(CloneNodeChildren { id, new_ids });
}
pub(crate) fn first_child(&mut self) {
self.edits.push(FirstChild {});
}
pub(crate) fn next_sibling(&mut self) {
self.edits.push(NextSibling {});
}
pub(crate) fn parent_node(&mut self) {
self.edits.push(ParentNode {});
}
pub(crate) fn store_with_id(&mut self, id: u64) {
self.edits.push(StoreWithId { id });
}
pub(crate) fn set_last_node(&mut self, id: u64) {
self.edits.push(SetLastNode { id });
}
}
// refs are only assigned once
pub struct NodeRefMutation<'a> {
pub element: &'a mut Option<once_cell::sync::OnceCell<Box<dyn Any>>>,
pub element_id: ElementId,
}
impl<'a> std::fmt::Debug for NodeRefMutation<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NodeRefMutation")
.field("element_id", &self.element_id)
.finish()
}
}
impl<'a> NodeRefMutation<'a> {
pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
self.element
.as_ref()
.and_then(|f| f.get())
.and_then(|f| f.downcast_ref::<T>())
}
pub fn downcast_mut<T: 'static>(&mut self) -> Option<&mut T> {
self.element
.as_mut()
.and_then(|f| f.get_mut())
.and_then(|f| f.downcast_mut::<T>())
}
}
-866
View File
@@ -1,866 +0,0 @@
//! Virtual Node Support
//!
//! VNodes represent lazily-constructed VDom trees that support diffing and event handlers. These VNodes should be *very*
//! cheap and *very* fast to construct - building a full tree should be quick.
use crate::{
innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
lazynodes::LazyNodes,
AnyEvent, Component,
};
use bumpalo::{boxed::Box as BumpBox, Bump};
use std::{
cell::{Cell, RefCell},
fmt::{Arguments, Debug, Formatter},
};
use crate::template::*;
/// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
///
/// 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 [`NodeFactory`] API
pub enum VNode<'src> {
/// Text VNodes are simply bump-allocated (or static) string slices
///
/// # Example
///
/// ```rust, ignore
/// let mut vdom = VirtualDom::new();
/// let node = vdom.render_vnode(rsx!( "hello" ));
///
/// if let VNode::Text(vtext) = node {
/// assert_eq!(vtext.text, "hello");
/// assert_eq!(vtext.dom_id.get(), None);
/// assert_eq!(vtext.is_static, true);
/// }
/// ```
Text(&'src VText<'src>),
/// Element VNodes are VNodes that may contain attributes, listeners, a key, a tag, and children.
///
/// # Example
///
/// ```rust, ignore
/// let mut vdom = VirtualDom::new();
///
/// let node = vdom.render_vnode(rsx!{
/// div {
/// key: "a",
/// onclick: |e| log::info!("clicked"),
/// hidden: "true",
/// style: { background_color: "red" },
/// "hello"
/// }
/// });
///
/// if let VNode::Element(velement) = node {
/// assert_eq!(velement.tag_name, "div");
/// assert_eq!(velement.namespace, None);
/// assert_eq!(velement.key, Some("a"));
/// }
/// ```
Element(&'src VElement<'src>),
/// Fragment nodes may contain many VNodes without a single root.
///
/// # Example
///
/// ```rust, ignore
/// rsx!{
/// a {}
/// link {}
/// style {}
/// "asd"
/// Example {}
/// }
/// ```
Fragment(&'src VFragment<'src>),
/// Component nodes represent a mounted component with props, children, and a key.
///
/// # Example
///
/// ```rust, ignore
/// fn Example(cx: Scope) -> Element {
/// ...
/// }
///
/// let mut vdom = VirtualDom::new();
///
/// let node = vdom.render_vnode(rsx!( Example {} ));
///
/// if let VNode::Component(vcomp) = node {
/// assert_eq!(vcomp.user_fc, Example as *const ());
/// }
/// ```
Component(&'src VComponent<'src>),
/// Templates are containers for other nodes
///
///
Template(&'src VTemplate<'src>),
}
impl<'src> VNode<'src> {
/// Get the VNode's "key" used in the keyed diffing algorithm.
pub fn key(&self) -> Option<&'src str> {
match &self {
VNode::Element(el) => el.key,
VNode::Component(c) => c.key,
VNode::Text(_t) => None,
VNode::Template(_t) => None,
VNode::Fragment(_f) => None,
}
}
/// Get the ElementID of the mounted VNode.
///
/// Panics if the mounted ID is None or if the VNode is not represented by a single Element.
pub fn mounted_id(&self) -> ElementId {
self.try_mounted_id().unwrap()
}
/// Try to get the ElementID of the mounted VNode.
///
/// Returns None if the VNode is not mounted, or if the VNode cannot be presented by a mounted ID (Fragment/Component)
pub fn try_mounted_id(&self) -> Option<ElementId> {
match &self {
VNode::Text(el) => el.id.get(),
VNode::Element(el) => el.id.get(),
VNode::Fragment(_) => None,
VNode::Component(_) => None,
VNode::Template(_) => todo!(),
}
}
// Create an "owned" version of the vnode.
pub(crate) fn decouple(&self) -> VNode<'src> {
match *self {
VNode::Text(t) => VNode::Text(t),
VNode::Element(e) => VNode::Element(e),
VNode::Component(c) => VNode::Component(c),
VNode::Fragment(f) => VNode::Fragment(f),
VNode::Template(_) => todo!(),
}
}
}
impl Debug for VNode<'_> {
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match &self {
VNode::Element(el) => s
.debug_struct("VNode::Element")
.field("name", &el.tag)
.field("key", &el.key)
.field("attrs", &el.attributes)
.field("children", &el.children)
.field("id", &el.id)
.finish(),
VNode::Text(t) => s
.debug_struct("VNode::Text")
.field("text", &t.text)
.field("id", &t.id)
.finish(),
VNode::Fragment(frag) => s
.debug_struct("VNode::Fragment")
.field("children", &frag.children)
.finish(),
VNode::Component(comp) => s
.debug_struct("VNode::Component")
.field("name", &comp.fn_name)
.field("fnptr", &comp.user_fc)
.field("key", &comp.key)
.field("scope", &comp.scope)
.finish(),
VNode::Template(_) => todo!(),
}
}
}
/// An Element's unique identifier.
///
/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
/// unmounted, then the `ElementId` will be reused for a new component.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct ElementId(pub usize);
impl std::fmt::Display for ElementId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl ElementId {
/// Convertt the ElementId to a `u64`.
pub fn as_u64(self) -> u64 {
self.0 as u64
}
}
fn empty_cell() -> Cell<Option<ElementId>> {
Cell::new(None)
}
/// A placeholder node only generated when Fragments don't have any children.
pub struct VPlaceholder {
/// The [`ElementId`] of the placeholder.
pub id: Cell<Option<ElementId>>,
}
/// A bump-allocated string slice and metadata.
pub struct VText<'src> {
/// The [`ElementId`] of the VText.
pub id: Cell<Option<ElementId>>,
/// The text of the VText.
pub text: &'src str,
/// An indiciation if this VText can be ignored during diffing
/// Is usually only when there are no strings to be formatted (so the text is &'static str)
pub is_static: bool,
}
/// A list of VNodes with no single root.
pub struct VFragment<'src> {
/// The key of the fragment to be used during keyed diffing.
pub key: Option<&'src str>,
pub memo: Option<FragmentMemo<'src>>,
/// Fragments can never have zero children. Enforced by NodeFactory.
///
/// You *can* make a fragment with no children, but it's not a valid fragment and your VDom will panic.
pub children: &'src [VNode<'src>],
}
pub struct FragmentMemo<'a> {
pub template: TemplateDef<'static>,
pub dynamic_nodes: &'a Cell<[ElementId]>,
}
/// An element like a "div" with children, listeners, and attributes.
pub struct VElement<'a> {
/// The [`ElementId`] of the VText.
pub id: Cell<Option<ElementId>>,
/// The key of the element to be used during keyed diffing.
pub key: Option<&'a str>,
/// The tag name of the element.
///
/// IE "div"
pub tag: &'static str,
/// The namespace of the VElement
///
/// IE "svg"
pub namespace: Option<&'static str>,
/// The parent of the Element (if any).
///
/// Used when bubbling events
pub parent: Cell<Option<ElementId>>,
/// The Listeners of the VElement.
pub listeners: &'a [Listener<'a>],
/// The attributes of the VElement.
pub attributes: &'a [Attribute<'a>],
/// The children of the VElement.
pub children: &'a [VNode<'a>],
}
impl Debug for VElement<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VElement")
.field("tag_name", &self.tag)
.field("namespace", &self.namespace)
.field("key", &self.key)
.field("id", &self.id)
.field("parent", &self.parent)
.field("listeners", &self.listeners.len())
.field("attributes", &self.attributes)
.field("children", &self.children)
.finish()
}
}
/// A trait for any generic Dioxus Element.
///
/// This trait provides the ability to use custom elements in the `rsx!` macro.
///
/// ```rust, ignore
/// struct my_element;
///
/// impl DioxusElement for my_element {
/// const TAG_NAME: "my_element";
/// const NAME_SPACE: None;
/// }
///
/// let _ = rsx!{
/// my_element {}
/// };
/// ```
pub trait DioxusElement {
/// The tag name of the element.
const TAG_NAME: &'static str;
/// The namespace of the element.
const NAME_SPACE: Option<&'static str>;
/// The tag name of the element.
#[inline]
fn tag_name(&self) -> &'static str {
Self::TAG_NAME
}
/// The namespace of the element.
#[inline]
fn namespace(&self) -> Option<&'static str> {
Self::NAME_SPACE
}
}
/// An attribute on a DOM node, such as `id="my-thing"` or
/// `href="https://example.com"`.
#[derive(Clone, Debug)]
pub struct Attribute<'a> {
/// The name of the attribute.
pub name: &'static str,
/// The value of the attribute.
pub value: AttributeValue<'a>,
/// An indication if this attribute can be ignored during diffing
///
/// Usually only when there are no strings to be formatted (so the value is &'static str)
pub is_static: bool,
/// An indication of we should always try and set the attribute.
/// Used in controlled components to ensure changes are propagated.
pub is_volatile: bool,
/// The namespace of the attribute.
///
/// Doesn't exist in the html spec.
/// Used in Dioxus to denote "style" tags and other attribute groups.
pub namespace: Option<&'static str>,
}
/// An event listener.
/// IE onclick, onkeydown, etc
pub struct Listener<'bump> {
/// The ID of the node that this listener is mounted to
/// Used to generate the event listener's ID on the DOM
pub mounted_node: Cell<Option<ElementId>>,
/// The type of event to listen for.
///
/// IE "click" - whatever the renderer needs to attach the listener by name.
pub event: &'static str,
/// The actual callback that the user specified
pub(crate) callback: InternalHandler<'bump>,
}
pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(AnyEvent) + 'bump>;
type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
///
/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
///
///
/// # Example
///
/// ```rust, ignore
///
/// rsx!{
/// MyComponent { onclick: move |evt| log::info!("clicked"), }
/// }
///
/// #[derive(Props)]
/// struct MyProps<'a> {
/// onclick: EventHandler<'a, MouseEvent>,
/// }
///
/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element {
/// cx.render(rsx!{
/// button {
/// onclick: move |evt| cx.props.onclick.call(evt),
/// }
/// })
/// }
///
/// ```
pub struct EventHandler<'bump, T = ()> {
/// The (optional) callback that the user specified
/// Uses a `RefCell` to allow for interior mutability, and FnMut closures.
pub callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
}
impl<'a, T> Default for EventHandler<'a, T> {
fn default() -> Self {
Self {
callback: RefCell::new(None),
}
}
}
impl<T> EventHandler<'_, T> {
/// Call this event handler with the appropriate event type
pub fn call(&self, event: T) {
if let Some(callback) = self.callback.borrow_mut().as_mut() {
callback(event);
}
}
/// Forcibly drop the internal handler callback, releasing memory
pub fn release(&self) {
self.callback.replace(None);
}
}
/// Virtual Components for custom user-defined components
/// Only supports the functional syntax
pub struct VComponent<'src> {
/// The key of the component to be used during keyed diffing.
pub key: Option<&'src str>,
/// The ID of the component.
/// Will not be assigned until after the component has been initialized.
pub scope: Cell<Option<ScopeId>>,
/// An indication if the component is static (can be memozied)
pub can_memoize: bool,
/// The function pointer to the component's render function.
pub user_fc: ComponentPtr,
/// The actual name of the component.
pub fn_name: &'static str,
/// The props of the component.
pub props: RefCell<Option<Box<dyn AnyProps + 'src>>>,
}
pub(crate) struct VComponentProps<P> {
pub render_fn: Component<P>,
pub memo: unsafe fn(&P, &P) -> bool,
pub props: P,
}
pub trait AnyProps {
fn as_ptr(&self) -> *const ();
fn render<'a>(&'a self, bump: &'a ScopeState) -> Element<'a>;
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
}
impl<P> AnyProps for VComponentProps<P> {
fn as_ptr(&self) -> *const () {
&self.props as *const _ as *const ()
}
// Safety:
// this will downcast the other ptr as our swallowed type!
// you *must* make this check *before* calling this method
// if your functions are not the same, then you will downcast a pointer into a different type (UB)
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool {
let real_other: &P = &*(other.as_ptr() as *const _ as *const P);
let real_us: &P = &*(self.as_ptr() as *const _ as *const P);
(self.memo)(real_us, real_other)
}
fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
let props = unsafe { std::mem::transmute::<&P, &P>(&self.props) };
(self.render_fn)(Scope { scope, props })
}
}
/// This struct provides an ergonomic API to quickly build VNodes.
///
/// NodeFactory is used to build VNodes in the component's memory space.
/// This struct adds metadata to the final VNode about listeners, attributes, and children
#[derive(Copy, Clone)]
pub struct NodeFactory<'a> {
pub(crate) scope: &'a ScopeState,
pub(crate) bump: &'a Bump,
}
impl<'a> NodeFactory<'a> {
/// Create a new [`NodeFactory`] from a [`Scope`] or [`ScopeState`]
pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> {
NodeFactory {
scope,
bump: &scope.wip_frame().bump,
}
}
/// Get the custom allocator for this component
#[inline]
pub fn bump(&self) -> &'a bumpalo::Bump {
self.bump
}
/// Directly pass in text blocks without the need to use the format_args macro.
pub fn static_text(&self, text: &'static str) -> VNode<'a> {
VNode::Text(self.bump.alloc(VText {
id: empty_cell(),
text,
is_static: true,
}))
}
/// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static
///
/// Text that's static may be pointer compared, making it cheaper to diff
pub fn raw_text(&self, args: Arguments) -> (&'a str, bool) {
match args.as_str() {
Some(static_str) => (static_str, true),
None => {
use bumpalo::core_alloc::fmt::Write;
let mut str_buf = bumpalo::collections::String::new_in(self.bump);
str_buf.write_fmt(args).unwrap();
(str_buf.into_bump_str(), false)
}
}
}
/// Create some text that's allocated along with the other vnodes
///
pub fn text(&self, args: Arguments) -> VNode<'a> {
let (text, is_static) = self.raw_text(args);
VNode::Text(self.bump.alloc(VText {
text,
is_static,
id: empty_cell(),
}))
}
/// Create a new [`VNode::Element`]
pub fn element(
&self,
el: impl DioxusElement,
listeners: &'a [Listener<'a>],
attributes: &'a [Attribute<'a>],
children: &'a [VNode<'a>],
key: Option<Arguments>,
) -> VNode<'a> {
self.raw_element(
el.tag_name(),
el.namespace(),
listeners,
attributes,
children,
key,
)
}
/// Create a new [`VNode::Element`] without the trait bound
///
/// IE pass in "div" instead of `div`
pub fn raw_element(
&self,
tag_name: &'static str,
namespace: Option<&'static str>,
listeners: &'a [Listener<'a>],
attributes: &'a [Attribute<'a>],
children: &'a [VNode<'a>],
key: Option<Arguments>,
) -> VNode<'a> {
let key = key.map(|f| self.raw_text(f).0);
let mut items = self.scope.items.borrow_mut();
for listener in listeners {
let long_listener = unsafe { std::mem::transmute(listener) };
items.listeners.push(long_listener);
}
VNode::Element(self.bump.alloc(VElement {
tag: tag_name,
key,
namespace,
listeners,
attributes,
children,
id: empty_cell(),
parent: empty_cell(),
}))
}
/// Create a new [`Attribute`]
pub fn attr(
&self,
name: &'static str,
val: Arguments,
namespace: Option<&'static str>,
is_volatile: bool,
) -> Attribute<'a> {
let (value, is_static) = self.raw_text(val);
Attribute {
name,
value: AttributeValue::Text(value),
is_static,
namespace,
is_volatile,
}
}
/// Create a new [`Attribute`] using non-arguments
pub fn custom_attr(
&self,
name: &'static str,
value: AttributeValue<'a>,
namespace: Option<&'static str>,
is_volatile: bool,
is_static: bool,
) -> Attribute<'a> {
Attribute {
name,
value,
is_static,
namespace,
is_volatile,
}
}
/// Create a new [`VNode::Component`]
pub fn component<P>(
&self,
component: fn(Scope<'a, P>) -> Element,
props: P,
key: Option<Arguments>,
fn_name: &'static str,
) -> VNode<'a>
where
P: Properties + 'a,
{
let vcomp = self.bump.alloc(VComponent {
key: key.map(|f| self.raw_text(f).0),
scope: Default::default(),
can_memoize: P::IS_STATIC,
user_fc: component as ComponentPtr,
fn_name,
props: RefCell::new(Some(Box::new(VComponentProps {
props,
memo: P::memoize, // smuggle the memoization function across borders
// i'm sorry but I just need to bludgeon the lifetimes into place here
// this is safe because we're managing all lifetimes to originate from previous calls
// the intricacies of Rust's lifetime system make it difficult to properly express
// the transformation from this specific lifetime to the for<'a> lifetime
render_fn: unsafe { std::mem::transmute(component) },
}))),
});
if !P::IS_STATIC {
let vcomp = &*vcomp;
let vcomp = unsafe { std::mem::transmute(vcomp) };
self.scope.items.borrow_mut().borrowed_props.push(vcomp);
}
VNode::Component(vcomp)
}
/// Create a new [`Listener`]
pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> {
Listener {
event,
mounted_node: Cell::new(None),
callback,
}
}
pub fn template(&self, def: &'static TemplateDef, dynamic_nodes: &'a [VNode<'a>]) -> VNode<'a> {
VNode::Template(self.bump.alloc(VTemplate {
def,
dynamic_nodes,
rendered_nodes: &[],
}))
}
// /// Create a new [`VNode::Fragment`] from a root of the rsx! call
// pub fn fragment_root<'b, 'c>(
// self,
// node_iter: impl IntoIterator<Item = impl IntoVNode<'a> + 'c> + 'b,
// ) -> VNode<'a> {
// let mut nodes = bumpalo::collections::Vec::new_in(self.bump);
// for node in node_iter {
// nodes.push(node.into_vnode(self));
// }
// if nodes.is_empty() {
// VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() }))
// } else {
// VNode::Fragment(self.bump.alloc(VFragment {
// children: nodes.into_bump_slice(),
// key: None,
// }))
// }
// }
// /// Create a new [`VNode::Fragment`] from any iterator
// pub fn fragment_from_iter<'c, I, J>(
// self,
// node_iter: impl IntoVNode<'a, I, J> + 'c,
// ) -> VNode<'a> {
// node_iter.into_vnode(self)
// }
// /// Create a new [`VNode`] from any iterator of children
// pub fn create_children(
// self,
// node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
// ) -> Element<'a> {
// let mut nodes = bumpalo::collections::Vec::new_in(self.bump);
// for node in node_iter {
// nodes.push(node.into_vnode(self));
// }
// if nodes.is_empty() {
// Some(VNode::Placeholder(
// self.bump.alloc(VPlaceholder { id: empty_cell() }),
// ))
// } else {
// let children = nodes.into_bump_slice();
// Some(VNode::Fragment(self.bump.alloc(VFragment {
// children,
// key: None,
// })))
// }
// }
/// Create a new [`EventHandler`] from an [`FnMut`]
pub fn event_handler<T>(self, f: impl FnMut(T) + 'a) -> EventHandler<'a, T> {
let handler: &mut dyn FnMut(T) = self.bump.alloc(f);
let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) };
let callback = RefCell::new(Some(caller));
EventHandler { callback }
}
}
impl Debug for NodeFactory<'_> {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
/// Trait implementations for use in the rsx! and html! macros.
///
/// ## Details
///
/// This section provides convenience methods and trait implementations for converting common structs into a format accepted
/// by the macros.
///
/// All dynamic content in the macros must flow in through `fragment_from_iter`. Everything else must be statically layed out.
/// We pipe basically everything through `fragment_from_iter`, so we expect a very specific type:
/// ```rust, ignore
/// impl IntoIterator<Item = impl IntoVNode<'a>>
/// ```
///
/// As such, all node creation must go through the factory, which is only available in the component context.
/// These strict requirements make it possible to manage lifetimes and state.
pub trait IntoVNode<'a, I = (), J = ()> {
/// Convert this into a [`VNode`], using the [`NodeFactory`] as a source of allocation
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
}
// TODO: do we even need this? It almost seems better not to
// // For the case where a rendered VNode is passed into the rsx! macro through curly braces
impl<'a> IntoVNode<'a> for VNode<'a> {
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
self
}
}
// Conveniently, we also support "null" (nothing) passed in
impl IntoVNode<'_> for () {
fn into_vnode(self, cx: NodeFactory) -> VNode {
todo!()
// VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() }))
}
}
impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
self.call(cx)
}
}
impl<'b> IntoVNode<'_> for &'b str {
fn into_vnode(self, cx: NodeFactory) -> VNode {
cx.text(format_args!("{}", self))
}
}
impl IntoVNode<'_> for String {
fn into_vnode(self, cx: NodeFactory) -> VNode {
cx.text(format_args!("{}", self))
}
}
impl IntoVNode<'_> for Arguments<'_> {
fn into_vnode(self, cx: NodeFactory) -> VNode {
cx.text(self)
}
}
impl<'a> IntoVNode<'a> for &VNode<'a> {
fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> {
// borrowed nodes are strange
self.decouple()
}
}
// Note that we're using the E as a generic but this is never crafted anyways.
pub struct FromNodeIterator;
impl<'a, T, I, E> IntoVNode<'a, FromNodeIterator, E> for T
where
T: IntoIterator<Item = I>,
I: IntoVNode<'a, E>,
{
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
let mut nodes = bumpalo::collections::Vec::new_in(cx.bump);
for node in self {
nodes.push(node.into_vnode(cx));
}
todo!()
// if nodes.is_empty() {
// VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() }))
// } else {
// let children = nodes.into_bump_slice();
// if cfg!(debug_assertions)
// && children.len() > 1
// && children.last().unwrap().key().is_none()
// {
// // todo: make the backtrace prettier or remove it altogether
// log::error!(
// r#"
// Warning: Each child in an array or iterator should have a unique "key" prop.
// Not providing a key will lead to poor performance with lists.
// See docs.rs/dioxus for more information.
// -------------
// {:?}
// "#,
// backtrace::Backtrace::new()
// );
// }
// VNode::Fragment(cx.bump.alloc(VFragment {
// children,
// key: None,
// }))
// }
}
}
@@ -1,163 +0,0 @@
use std::{
any::Any,
fmt::{Arguments, Formatter},
};
use bumpalo::Bump;
/// Possible values for an attribute
#[derive(Clone, Copy)]
pub enum AttributeValue<'a> {
/// Reference strs, most common
Text(&'a str),
/// Basic float values
Float(f32),
/// Basic Int values
Int(i32),
/// Basic bool values
Bool(bool),
/// Everything else
Any(&'a dyn AnyAttributeValue),
}
impl<'a> PartialEq for AttributeValue<'a> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Text(l0), Self::Text(r0)) => l0 == r0,
(Self::Float(l0), Self::Float(r0)) => l0 == r0,
(Self::Int(l0), Self::Int(r0)) => l0 == r0,
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
(Self::Any(l0), Self::Any(r0)) => (*l0).cmp_any(*r0),
_ => false,
}
}
}
impl std::fmt::Debug for AttributeValue<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
AttributeValue::Text(s) => write!(f, "AttributeValue::Text({:?})", s),
AttributeValue::Float(v) => write!(f, "AttributeValue::Float({:?})", v),
AttributeValue::Int(v) => write!(f, "AttributeValue::Int({:?})", v),
AttributeValue::Bool(b) => write!(f, "AttributeValue::Bool({:?})", b),
AttributeValue::Any(_) => write!(f, "AttributeValue::Any()"),
}
}
}
/// A value that can be converted into an attribute value
pub trait IntoAttributeValue<'a> {
/// Convert into an attribute value
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
}
impl<'a> IntoAttributeValue<'a> for &'a str {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Text(self)
}
}
impl<'a> IntoAttributeValue<'a> for f32 {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Float(self)
}
}
impl<'a> IntoAttributeValue<'a> for i32 {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Int(self)
}
}
impl<'a> IntoAttributeValue<'a> for bool {
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Bool(self)
}
}
impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
use bumpalo::core_alloc::fmt::Write;
let mut str_buf = bumpalo::collections::String::new_in(bump);
str_buf.write_fmt(self).unwrap();
AttributeValue::Text(str_buf.into_bump_str())
}
}
// todo
#[allow(missing_docs)]
impl<'a> AttributeValue<'a> {
pub fn is_truthy(&self) -> bool {
match self {
AttributeValue::Text(t) => *t == "true",
AttributeValue::Bool(t) => *t,
_ => false,
}
}
pub fn is_falsy(&self) -> bool {
match self {
AttributeValue::Text(t) => *t == "false",
AttributeValue::Bool(t) => !(*t),
_ => false,
}
}
}
/// A trait that allows for comparing two values of the same type through the Any trait
///
/// Defaults to false if the types are not the same
///
/// This is an implicit trait, so any value that is 'static and PartialEq can be used directly
///
/// If you want to override the default behavior, you should implement PartialEq through a wrapper type
pub trait AnyAttributeValue: Any {
/// Perform a comparison between two values
fn cmp_any<'a>(&'a self, _other: &'a dyn AnyAttributeValue) -> bool {
false
}
}
impl<T: Any + PartialEq> AnyAttributeValue for T {
fn cmp_any(&self, other: &dyn AnyAttributeValue) -> bool {
// we can't, for whatever reason use other as &dyn Any
let right: &dyn Any = unsafe { std::mem::transmute(other) };
match right.downcast_ref::<T>() {
Some(right) => self == right,
None => false,
}
}
}
#[test]
fn cmp_any_works_even_though_it_transmutes() {
// same type, same value
let a = 2;
let b = 2;
assert!(a.cmp_any(&b as &dyn AnyAttributeValue));
// same type, different value
let a = "asds";
let b = "asdsasd";
assert!(!a.cmp_any(&b as &dyn AnyAttributeValue));
// different type, different values
let a = 123;
let b = "asdsasd";
assert!(!a.cmp_any(&b as &dyn AnyAttributeValue));
// Custom structs
#[derive(PartialEq)]
struct CustomStruct {
a: i32,
}
let a = CustomStruct { a: 1 };
let b = CustomStruct { a: 1 };
assert!(a.cmp_any(&b as &dyn AnyAttributeValue));
let a = CustomStruct { a: 1 };
let b = CustomStruct { a: 123 };
assert!(!a.cmp_any(&b as &dyn AnyAttributeValue));
let a = CustomStruct { a: 1 };
let b = "asdasd";
assert!(!a.cmp_any(&b as &dyn AnyAttributeValue));
}
-61
View File
@@ -1,61 +0,0 @@
use crate::{
innerlude::{ComponentPtr, Element, Scope, ScopeId, ScopeState},
Component,
};
use std::cell::{Cell, RefCell};
/// Virtual Components for custom user-defined components
/// Only supports the functional syntax
pub struct VComponent<'src> {
/// The key of the component to be used during keyed diffing.
pub key: Option<&'src str>,
/// The ID of the component.
/// Will not be assigned until after the component has been initialized.
pub scope: Cell<Option<ScopeId>>,
/// An indication if the component is static (can be memozied)
pub can_memoize: bool,
/// The function pointer to the component's render function.
pub user_fc: ComponentPtr,
/// The actual name of the component.
pub fn_name: &'static str,
/// The props of the component.
pub props: RefCell<Option<Box<dyn AnyProps + 'src>>>,
}
pub(crate) struct VComponentProps<P> {
pub render_fn: Component<P>,
pub memo: unsafe fn(&P, &P) -> bool,
pub props: P,
}
pub trait AnyProps {
fn as_ptr(&self) -> *const ();
fn render<'a>(&'a self, bump: &'a ScopeState) -> Element<'a>;
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
}
impl<P> AnyProps for VComponentProps<P> {
fn as_ptr(&self) -> *const () {
&self.props as *const _ as *const ()
}
// Safety:
// this will downcast the other ptr as our swallowed type!
// you *must* make this check *before* calling this method
// if your functions are not the same, then you will downcast a pointer into a different type (UB)
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool {
let real_other: &P = &*(other.as_ptr() as *const _ as *const P);
let real_us: &P = &*(self.as_ptr() as *const _ as *const P);
(self.memo)(real_us, real_other)
}
fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> {
let props = unsafe { std::mem::transmute::<&P, &P>(&self.props) };
(self.render_fn)(Scope { scope, props })
}
}
-150
View File
@@ -1,150 +0,0 @@
use crate::{innerlude::AttributeValue, AnyEvent, ElementId, VNode};
use bumpalo::boxed::Box as BumpBox;
use std::{
cell::{Cell, RefCell},
fmt::{Debug, Formatter},
};
/// An element like a "div" with children, listeners, and attributes.
pub struct VElement<'a> {
/// The [`ElementId`] of the VText.
pub id: Cell<Option<ElementId>>,
/// The key of the element to be used during keyed diffing.
pub key: Option<&'a str>,
/// The tag name of the element.
///
/// IE "div"
pub tag: &'static str,
/// The namespace of the VElement
///
/// IE "svg"
pub namespace: Option<&'static str>,
/// The parent of the Element (if any).
///
/// Used when bubbling events
pub parent: Cell<Option<ElementId>>,
/// The Listeners of the VElement.
pub listeners: &'a [Listener<'a>],
/// The attributes of the VElement.
pub attributes: &'a [Attribute<'a>],
/// The children of the VElement.
pub children: &'a [VNode<'a>],
}
impl Debug for VElement<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VElement")
.field("tag_name", &self.tag)
.field("namespace", &self.namespace)
.field("key", &self.key)
.field("id", &self.id)
.field("parent", &self.parent)
.field("listeners", &self.listeners.len())
.field("attributes", &self.attributes)
.field("children", &self.children)
.finish()
}
}
/// An attribute on a DOM node, such as `id="my-thing"` or
/// `href="https://example.com"`.
#[derive(Clone, Debug)]
pub struct Attribute<'a> {
/// The name of the attribute.
pub name: &'static str,
/// The namespace of the attribute.
///
/// Doesn't exist in the html spec.
/// Used in Dioxus to denote "style" tags and other attribute groups.
pub namespace: Option<&'static str>,
/// An indication of we should always try and set the attribute.
/// Used in controlled components to ensure changes are propagated.
pub volatile: bool,
/// The value of the attribute.
pub value: AttributeValue<'a>,
}
/// An event listener.
/// IE onclick, onkeydown, etc
pub struct Listener<'bump> {
/// The ID of the node that this listener is mounted to
/// Used to generate the event listener's ID on the DOM
pub mounted_node: Cell<ElementId>,
/// The type of event to listen for.
///
/// IE "click" - whatever the renderer needs to attach the listener by name.
pub event: &'static str,
/// The actual callback that the user specified
pub(crate) callback: InternalHandler<'bump>,
}
pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(AnyEvent) + 'bump>;
type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
///
/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
///
///
/// # Example
///
/// ```rust, ignore
///
/// rsx!{
/// MyComponent { onclick: move |evt| log::info!("clicked"), }
/// }
///
/// #[derive(Props)]
/// struct MyProps<'a> {
/// onclick: EventHandler<'a, MouseEvent>,
/// }
///
/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element {
/// cx.render(rsx!{
/// button {
/// onclick: move |evt| cx.props.onclick.call(evt),
/// }
/// })
/// }
///
/// ```
pub struct EventHandler<'bump, T = ()> {
/// The (optional) callback that the user specified
/// Uses a `RefCell` to allow for interior mutability, and FnMut closures.
pub callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
}
impl<'a, T> Default for EventHandler<'a, T> {
fn default() -> Self {
Self {
callback: RefCell::new(None),
}
}
}
impl<T> EventHandler<'_, T> {
/// Call this event handler with the appropriate event type
pub fn call(&self, event: T) {
if let Some(callback) = self.callback.borrow_mut().as_mut() {
callback(event);
}
}
/// Forcibly drop the internal handler callback, releasing memory
pub fn release(&self) {
self.callback.replace(None);
}
}
-362
View File
@@ -1,362 +0,0 @@
use crate::{innerlude::*, Attribute, Listener, VElement, VNode, VText};
use bumpalo::{boxed::Box as BumpBox, Bump};
use std::{
cell::{Cell, RefCell},
fmt::{Arguments, Debug},
};
/// This struct provides an ergonomic API to quickly build VNodes.
///
/// NodeFactory is used to build VNodes in the component's memory space.
/// This struct adds metadata to the final VNode about listeners, attributes, and children
#[derive(Copy, Clone)]
pub struct NodeFactory<'a> {
pub(crate) scope: &'a ScopeState,
pub(crate) bump: &'a Bump,
}
impl<'a> NodeFactory<'a> {
/// Create a new [`NodeFactory`] from a [`Scope`] or [`ScopeState`]
pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> {
NodeFactory {
scope,
bump: &scope.wip_frame().bump,
}
}
/// Get the custom allocator for this component
#[inline]
pub fn bump(&self) -> &'a bumpalo::Bump {
self.bump
}
/// Directly pass in text blocks without the need to use the format_args macro.
pub fn static_text(&self, text: &'static str) -> VNode<'a> {
VNode::Text(self.bump.alloc(VText {
id: Default::default(),
text,
}))
}
/// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static
///
/// Text that's static may be pointer compared, making it cheaper to diff
pub fn raw_text(&self, args: Arguments) -> (&'a str, bool) {
match args.as_str() {
Some(static_str) => (static_str, true),
None => {
use bumpalo::core_alloc::fmt::Write;
let mut str_buf = bumpalo::collections::String::new_in(self.bump);
str_buf.write_fmt(args).unwrap();
(str_buf.into_bump_str(), false)
}
}
}
/// Create some text that's allocated along with the other vnodes
///
pub fn text(&self, args: Arguments) -> VNode<'a> {
let (text, _is_static) = self.raw_text(args);
VNode::Text(self.bump.alloc(VText {
text,
id: Default::default(),
}))
}
/// Create a new [`VNode::Element`] without the trait bound
///
/// IE pass in "div" instead of `div`
pub fn raw_element(
&self,
tag_name: &'static str,
namespace: Option<&'static str>,
listeners: &'a [Listener<'a>],
attributes: &'a [Attribute<'a>],
children: &'a [VNode<'a>],
key: Option<Arguments>,
) -> VNode<'a> {
let key = key.map(|f| self.raw_text(f).0);
let mut items = self.scope.items.borrow_mut();
for listener in listeners {
let long_listener = unsafe { std::mem::transmute(listener) };
items.listeners.push(long_listener);
}
VNode::Element(self.bump.alloc(VElement {
tag: tag_name,
key,
namespace,
listeners,
attributes,
children,
id: Default::default(),
parent: Default::default(),
}))
}
/// Create a new [`Attribute`]
pub fn attr(
&self,
name: &'static str,
val: impl IntoAttributeValue<'a>,
namespace: Option<&'static str>,
is_volatile: bool,
) -> Attribute<'a> {
Attribute {
name,
namespace,
volatile: is_volatile,
value: val.into_value(self.bump),
}
}
/// Create a new [`Attribute`] using non-arguments
pub fn custom_attr(
&self,
name: &'static str,
value: AttributeValue<'a>,
namespace: Option<&'static str>,
is_volatile: bool,
) -> Attribute<'a> {
Attribute {
name,
namespace,
volatile: is_volatile,
value,
}
}
/// Create a new [`VNode::Component`]
pub fn component<P>(
&self,
component: fn(Scope<'a, P>) -> Element,
props: P,
key: Option<Arguments>,
fn_name: &'static str,
) -> VNode<'a>
where
P: Properties + 'a,
{
let vcomp = self.bump.alloc(VComponent {
key: key.map(|f| self.raw_text(f).0),
scope: Default::default(),
can_memoize: P::IS_STATIC,
user_fc: component as ComponentPtr,
fn_name,
props: RefCell::new(Some(Box::new(VComponentProps {
props,
memo: P::memoize, // smuggle the memoization function across borders
// i'm sorry but I just need to bludgeon the lifetimes into place here
// this is safe because we're managing all lifetimes to originate from previous calls
// the intricacies of Rust's lifetime system make it difficult to properly express
// the transformation from this specific lifetime to the for<'a> lifetime
render_fn: unsafe { std::mem::transmute(component) },
}))),
});
if !P::IS_STATIC {
let vcomp = &*vcomp;
let vcomp = unsafe { std::mem::transmute(vcomp) };
self.scope.items.borrow_mut().borrowed_props.push(vcomp);
}
VNode::Component(vcomp)
}
/// Create a new [`Listener`]
pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> {
Listener {
event,
mounted_node: Cell::new(ElementId(0)),
callback,
}
}
/// Create a new [`VNode::Fragment`] from a root of the rsx! call
pub fn fragment_root<'b, 'c>(
self,
node_iter: impl IntoIterator<Item = impl IntoVNode<'a> + 'c> + 'b,
) -> VNode<'a> {
let mut nodes = bumpalo::collections::Vec::new_in(self.bump);
for node in node_iter {
nodes.push(node.into_vnode(self));
}
VNode::Fragment(self.bump.alloc(VFragment {
children: nodes.into_bump_slice(),
placeholder: Default::default(),
key: None,
}))
}
/// Create a new [`VNode::Fragment`] from any iterator
pub fn fragment_from_iter<'c, I, J>(
self,
node_iter: impl IntoVNode<'a, I, J> + 'c,
) -> VNode<'a> {
node_iter.into_vnode(self)
}
/// Create a new [`VNode`] from any iterator of children
pub fn create_children(
self,
node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
) -> Element<'a> {
let mut nodes = bumpalo::collections::Vec::new_in(self.bump);
for node in node_iter {
nodes.push(node.into_vnode(self));
}
let children = nodes.into_bump_slice();
Some(VNode::Fragment(self.bump.alloc(VFragment {
children,
key: None,
placeholder: Default::default(),
})))
}
/// Create a new [`EventHandler`] from an [`FnMut`]
pub fn event_handler<T>(self, f: impl FnMut(T) + 'a) -> EventHandler<'a, T> {
let handler: &mut dyn FnMut(T) = self.bump.alloc(f);
let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) };
let callback = RefCell::new(Some(caller));
EventHandler { callback }
}
/// Create a refrence to a template
pub fn template_ref(
&self,
template: Template<'static>,
nodes: &'a [VNode<'a>],
attributes: &'a [Attribute<'a>],
listeners: &'a [Listener<'a>],
key: Option<Arguments>,
) -> VNode<'a> {
VNode::Template(
self.bump.alloc(VTemplate {
key: None,
node_id: Cell::new(ElementId(0)),
template,
dynamic_nodes: self.bump.alloc([]),
dynamic_attrs: self.bump.alloc([]),
listeners,
root_ids:
bumpalo::vec![in self.bump; Cell::new(ElementId(0)); template.roots.len()]
.into_bump_slice(),
}),
)
}
}
impl Debug for NodeFactory<'_> {
fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
/// Trait implementations for use in the rsx! and html! macros.
///
/// ## Details
///
/// This section provides convenience methods and trait implementations for converting common structs into a format accepted
/// by the macros.
///
/// All dynamic content in the macros must flow in through `fragment_from_iter`. Everything else must be statically layed out.
/// We pipe basically everything through `fragment_from_iter`, so we expect a very specific type:
/// ```rust, ignore
/// impl IntoIterator<Item = impl IntoVNode<'a>>
/// ```
///
/// As such, all node creation must go through the factory, which is only available in the component context.
/// These strict requirements make it possible to manage lifetimes and state.
pub trait IntoVNode<'a, I = (), J = ()> {
/// Convert this into a [`VNode`], using the [`NodeFactory`] as a source of allocation
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
}
// TODO: do we even need this? It almost seems better not to
// // For the case where a rendered VNode is passed into the rsx! macro through curly braces
impl<'a> IntoVNode<'a> for VNode<'a> {
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
self
}
}
impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, '_> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
self.call(cx)
}
}
impl<'b> IntoVNode<'_> for &'b str {
fn into_vnode(self, cx: NodeFactory) -> VNode {
cx.text(format_args!("{}", self))
}
}
impl IntoVNode<'_> for String {
fn into_vnode(self, cx: NodeFactory) -> VNode {
cx.text(format_args!("{}", self))
}
}
impl IntoVNode<'_> for Arguments<'_> {
fn into_vnode(self, cx: NodeFactory) -> VNode {
cx.text(self)
}
}
impl<'a> IntoVNode<'a> for &VNode<'a> {
fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> {
// borrowed nodes are strange
self.decouple()
}
}
// Note that we're using the E as a generic but this is never crafted anyways.
pub struct FromNodeIterator;
impl<'a, T, I, E> IntoVNode<'a, FromNodeIterator, E> for T
where
T: IntoIterator<Item = I>,
I: IntoVNode<'a, E>,
{
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
let mut nodes = bumpalo::collections::Vec::new_in(cx.bump);
for node in self {
nodes.push(node.into_vnode(cx));
}
let children = nodes.into_bump_slice();
if cfg!(debug_assertions) && children.len() > 1 && children.last().unwrap().key().is_none()
{
// let bt = backtrace::Backtrace::new();
let bt = "no backtrace available";
// todo: make the backtrace prettier or remove it altogether
log::error!(
r#"
Warning: Each child in an array or iterator should have a unique "key" prop.
Not providing a key will lead to poor performance with lists.
See docs.rs/dioxus for more information.
-------------
{:?}
"#,
bt
);
}
VNode::Fragment(cx.bump.alloc(VFragment {
children,
placeholder: Default::default(),
key: None,
}))
}
}
-16
View File
@@ -1,16 +0,0 @@
use crate::{ElementId, VNode};
use std::cell::Cell;
/// A list of VNodes with no single root.
pub struct VFragment<'src> {
/// The key of the fragment to be used during keyed diffing.
pub key: Option<&'src str>,
/// The [`ElementId`] of the placeholder if it exists
pub placeholder: Cell<Option<ElementId>>,
/// Fragments can never have zero children. Enforced by NodeFactory.
///
/// You *can* make a fragment with no children, but it's not a valid fragment and your VDom will panic.
pub children: &'src [VNode<'src>],
}
-207
View File
@@ -1,207 +0,0 @@
//! Virtual Node Support
//!
//! VNodes represent lazily-constructed VDom trees that support diffing and event handlers. These VNodes should be *very*
//! cheap and *very* fast to construct - building a full tree should be quick.
use std::fmt::{Debug, Formatter};
mod arbitrary_value;
mod component;
mod element;
mod factory;
mod fragment;
mod placeholder;
mod suspense;
mod template;
mod text;
pub use arbitrary_value::*;
pub use component::*;
pub use element::*;
pub use factory::*;
pub use fragment::*;
pub use suspense::*;
pub use template::*;
pub use text::*;
use self::placeholder::VPlaceholder;
/// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
///
/// 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 [`NodeFactory`] API
pub enum VNode<'src> {
/// Text VNodes are simply bump-allocated (or static) string slices
///
/// # Example
///
/// ```rust, ignore
/// let mut vdom = VirtualDom::new();
/// let node = vdom.render_vnode(rsx!( "hello" ));
///
/// if let VNode::Text(vtext) = node {
/// assert_eq!(vtext.text, "hello");
/// assert_eq!(vtext.dom_id.get(), None);
/// assert_eq!(vtext.is_static, true);
/// }
/// ```
Text(&'src VText<'src>),
/// Element VNodes are VNodes that may contain attributes, listeners, a key, a tag, and children.
///
/// # Example
///
/// ```rust, ignore
/// let mut vdom = VirtualDom::new();
///
/// let node = vdom.render_vnode(rsx!{
/// div {
/// key: "a",
/// onclick: |e| log::info!("clicked"),
/// hidden: "true",
/// style: { background_color: "red" },
/// "hello"
/// }
/// });
///
/// if let VNode::Element(velement) = node {
/// assert_eq!(velement.tag_name, "div");
/// assert_eq!(velement.namespace, None);
/// assert_eq!(velement.key, Some("a"));
/// }
/// ```
Element(&'src VElement<'src>),
/// Fragment nodes may contain many VNodes without a single root.
///
/// # Example
///
/// ```rust, ignore
/// rsx!{
/// a {}
/// link {}
/// style {}
/// "asd"
/// Example {}
/// }
/// ```
Fragment(&'src VFragment<'src>),
/// Component nodes represent a mounted component with props, children, and a key.
///
/// # Example
///
/// ```rust, ignore
/// fn Example(cx: Scope) -> Element {
/// ...
/// }
///
/// let mut vdom = VirtualDom::new();
///
/// let node = vdom.render_vnode(rsx!( Example {} ));
///
/// if let VNode::Component(vcomp) = node {
/// assert_eq!(vcomp.user_fc, Example as *const ());
/// }
/// ```
Component(&'src VComponent<'src>),
/// Templetes ase generated by the rsx macro to eleminate diffing static nodes.
///
///
///
///
///
///
Template(&'src VTemplate<'src>),
}
/// An Element's unique identifier.
///
/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
/// unmounted, then the `ElementId` will be reused for a new component.
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", serde(transparent))]
pub struct ElementId(pub usize);
impl<'src> VNode<'src> {
/// Get the VNode's "key" used in the keyed diffing algorithm.
pub fn key(&self) -> Option<&'src str> {
match &self {
VNode::Element(el) => el.key,
VNode::Component(c) => c.key,
VNode::Fragment(f) => f.key,
VNode::Template(t) => t.key,
VNode::Text(_t) => None,
}
}
/// Get the ElementID of the mounted VNode.
///
/// Panics if the mounted ID is None or if the VNode is not represented by a single Element.
pub fn mounted_id(&self) -> ElementId {
self.try_mounted_id().unwrap()
}
/// Try to get the ElementID of the mounted VNode.
///
/// Returns None if the VNode is not mounted, or if the VNode cannot be presented by a mounted ID (Fragment/Component)
pub fn try_mounted_id(&self) -> Option<ElementId> {
match &self {
VNode::Text(el) => el.id.get(),
VNode::Element(el) => el.id.get(),
VNode::Fragment(_) => None,
VNode::Component(_) => None,
VNode::Template(_) => None,
}
}
// Create an "owned" version of the vnode.
pub(crate) fn decouple(&self) -> VNode<'src> {
match *self {
VNode::Text(t) => VNode::Text(t),
VNode::Element(e) => VNode::Element(e),
VNode::Component(c) => VNode::Component(c),
VNode::Fragment(f) => VNode::Fragment(f),
VNode::Template(t) => VNode::Template(t),
}
}
}
impl Debug for VNode<'_> {
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match &self {
VNode::Element(el) => s
.debug_struct("VNode::Element")
.field("name", &el.tag)
.field("key", &el.key)
.field("attrs", &el.attributes)
.field("children", &el.children)
.field("id", &el.id)
.finish(),
VNode::Text(t) => s
.debug_struct("VNode::Text")
.field("text", &t.text)
.field("id", &t.id)
.finish(),
VNode::Fragment(frag) => s
.debug_struct("VNode::Fragment")
.field("children", &frag.children)
.finish(),
VNode::Component(comp) => s
.debug_struct("VNode::Component")
.field("name", &comp.fn_name)
.field("fnptr", &comp.user_fc)
.field("key", &comp.key)
.field("scope", &comp.scope)
.finish(),
VNode::Template(temp) => s
.debug_struct("VNode::Templates")
.field("template_id", &temp.template.id)
.finish(),
}
}
}
-88
View File
@@ -1,88 +0,0 @@
use std::{cell::Cell, num::NonZeroUsize};
/// A reference to a template along with any context needed to hydrate it
pub struct VTemplate<'a> {
// The ID assigned for the root of this template
pub node_id: Cell<ElementId>,
pub template: &'static Template,
/// All the dynamic nodes for a template
pub dynamic_nodes: &'a [DynamicNode<'a>],
pub dynamic_attrs: &'a [AttributeLocation<'a>],
}
#[derive(Debug, Clone, Copy)]
pub struct Template {
pub id: &'static str,
pub root: TemplateNode<'static>,
// todo: locations of dynamic nodes
pub node_pathways: &'static [&'static [u8]],
// todo: locations of dynamic nodes
pub attr_pathways: &'static [&'static [u8]],
}
/// A weird-ish variant of VNodes with way more limited types
#[derive(Debug, Clone, Copy)]
pub enum TemplateNode<'a> {
/// A simple element
Element {
tag: &'a str,
namespace: Option<&'a str>,
attrs: &'a [TemplateAttribute<'a>],
children: &'a [TemplateNode<'a>],
},
Text(&'a str),
Dynamic(usize),
DynamicText(usize),
}
pub enum DynamicNode<'a> {
// Anything declared in component form
// IE in caps or with underscores
Component {
name: &'static str,
},
// Comes in with string interpolation or from format_args, include_str, etc
Text {
id: Cell<ElementId>,
value: &'static str,
},
// Anything that's coming in as an iterator
Fragment {
children: &'a [VTemplate<'a>],
},
}
#[derive(Debug)]
pub struct TemplateAttribute<'a> {
pub name: &'static str,
pub value: &'a str,
pub namespace: Option<&'static str>,
pub volatile: bool,
}
pub struct AttributeLocation<'a> {
pub mounted_element: Cell<ElementId>,
pub attrs: &'a [Attribute<'a>],
}
#[derive(Debug)]
pub struct Attribute<'a> {
pub name: &'static str,
pub value: &'a str,
pub namespace: Option<&'static str>,
}
#[test]
fn what_are_the_sizes() {
dbg!(std::mem::size_of::<VTemplate>());
dbg!(std::mem::size_of::<Template>());
dbg!(std::mem::size_of::<TemplateNode>());
}
@@ -1,8 +0,0 @@
use crate::ElementId;
use std::cell::Cell;
/// A placeholder node
pub struct VPlaceholder {
pub id: Cell<Option<ElementId>>,
pub dynamic_index: Option<usize>,
}
Whitespace-only changes.
-78
View File
@@ -1,78 +0,0 @@
use std::{cell::Cell, hash::Hash};
use crate::{Attribute, ElementId, Listener, VNode};
/// A reference to a template along with any context needed to hydrate it
pub struct VTemplate<'a> {
pub key: Option<&'a str>,
// The ID assigned for all nodes in this template
pub node_id: Cell<ElementId>,
// All the IDs for the roots of this template
// Non-assigned IDs are set to 0
pub root_ids: &'a [Cell<ElementId>],
pub template: Template<'static>,
/// All the non-root dynamic nodes
pub dynamic_nodes: &'a [NodeLocation<'a>],
pub dynamic_attrs: &'a [AttributeLocation<'a>],
pub listeners: &'a [Listener<'a>],
}
/// A template that is created at compile time
#[derive(Clone, Copy)]
pub struct Template<'a> {
/// name, line, col, or some sort of identifier
pub id: &'static str,
/// All the roots of the template. ie rsx! { div {} div{} } would have two roots
pub roots: &'a [TemplateNode<'a>],
}
impl<'a> Eq for Template<'a> {}
impl<'a> PartialEq for Template<'a> {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl<'a> Hash for Template<'a> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.id.hash(state);
}
}
/// A weird-ish variant of VNodes with way more limited types
pub enum TemplateNode<'a> {
/// A simple element
Element {
tag: &'static str,
namespace: Option<&'static str>,
attrs: &'a [TemplateAttribute<'a>],
children: &'a [TemplateNode<'a>],
},
Text(&'static str),
Dynamic(usize),
}
pub struct TemplateAttribute<'a> {
pub name: &'static str,
pub value: &'a str,
pub namespace: Option<&'static str>,
pub volatile: bool,
}
pub struct AttributeLocation<'a> {
pub pathway: &'static [u8],
pub mounted_element: Cell<ElementId>,
pub attrs: &'a [Attribute<'a>],
pub listeners: &'a [Listener<'a>],
}
pub struct NodeLocation<'a> {
pub pathway: &'static [u8],
pub node: VNode<'a>,
}
-11
View File
@@ -1,11 +0,0 @@
use crate::ElementId;
use std::cell::Cell;
/// A bump-allocated string slice and metadata.
pub struct VText<'src> {
/// The [`ElementId`] of the VText.
pub id: Cell<Option<ElementId>>,
/// The text of the VText.
pub text: &'src str,
}
-170
View File
@@ -1,170 +0,0 @@
use crate::innerlude::*;
pub struct FragmentProps<'a>(Element<'a>);
pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
impl<'a> FragmentBuilder<'a, false> {
pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
FragmentBuilder(children)
}
}
impl<'a, const A: bool> FragmentBuilder<'a, A> {
pub fn build(self) -> FragmentProps<'a> {
FragmentProps(self.0)
}
}
/// Access the children elements passed into the component
///
/// This enables patterns where a component is passed children from its parent.
///
/// ## Details
///
/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
/// on the props that takes Context.
///
/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
/// props are valid for the static lifetime.
///
/// ## Example
///
/// ```rust, ignore
/// fn App(cx: Scope) -> Element {
/// cx.render(rsx!{
/// CustomCard {
/// h1 {}
/// p {}
/// }
/// })
/// }
///
/// #[derive(PartialEq, Props)]
/// struct CardProps {
/// children: Element
/// }
///
/// fn CustomCard(cx: Scope<CardProps>) -> Element {
/// cx.render(rsx!{
/// div {
/// h1 {"Title card"}
/// {cx.props.children}
/// }
/// })
/// }
/// ```
impl<'a> Properties for FragmentProps<'a> {
type Builder = FragmentBuilder<'a, false>;
const IS_STATIC: bool = false;
fn builder() -> Self::Builder {
FragmentBuilder(None)
}
unsafe fn memoize(&self, _other: &Self) -> bool {
false
}
}
// /// 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)))
// }
/// 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
/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
///
/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However,
/// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored.
/// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure);
///
/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However,
/// this is only safe if the props are 'static - otherwise you might borrow references after-free.
///
/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally,
/// we advise the use of smart pointers in cases where memoization is important.
///
/// ## Example
///
/// For props that are 'static:
/// ```rust, ignore
/// #[derive(Props, PartialEq)]
/// struct MyProps {
/// data: String
/// }
/// ```
///
/// For props that borrow:
///
/// ```rust, ignore
/// #[derive(Props)]
/// struct MyProps<'a >{
/// data: &'a str
/// }
/// ```
pub trait Properties: Sized {
/// The type of the builder for this component.
/// Used to create "in-progress" versions of the props.
type Builder;
/// An indication if these props are can be memoized automatically.
const IS_STATIC: bool;
/// Create a builder for this component.
fn builder() -> Self::Builder;
/// Memoization can only happen if the props are valid for the 'static lifetime
///
/// # Safety
/// The user must know if their props are static, but if they make a mistake, UB happens
/// Therefore it's unsafe to memoize.
unsafe fn memoize(&self, other: &Self) -> bool;
}
impl Properties for () {
type Builder = EmptyBuilder;
const IS_STATIC: bool = true;
fn builder() -> Self::Builder {
EmptyBuilder {}
}
unsafe fn memoize(&self, _other: &Self) -> bool {
true
}
}
// We allow components to use the () generic parameter if they have no props. This impl enables the "build" method
// that the macros use to anonymously complete prop construction.
pub struct EmptyBuilder;
impl EmptyBuilder {
pub fn build(self) {}
}
/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
/// to initialize a component's props.
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
T::builder()
}
File diff suppressed because it is too large. Load diff
-84
View File
@@ -1,84 +0,0 @@
use crate::innerlude::{VNode, VirtualDom};
/// An iterator that only yields "real" [`Element`]s. IE only Elements that are
/// not [`VNode::Component`] or [`VNode::Fragment`], .
pub struct ElementIdIterator<'a> {
vdom: &'a VirtualDom,
// Heuristically we should never bleed into 5 completely nested fragments/components
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
}
impl<'a> ElementIdIterator<'a> {
/// Create a new iterator from the given [`VirtualDom`] and [`VNode`]
///
/// This will allow you to iterate through all the real childrne of the [`VNode`].
pub fn new(vdom: &'a VirtualDom, node: &'a VNode<'a>) -> Self {
Self {
vdom,
stack: smallvec::smallvec![(0, node)],
}
}
}
impl<'a> Iterator for ElementIdIterator<'a> {
type Item = &'a VNode<'a>;
fn next(&mut self) -> Option<&'a VNode<'a>> {
let mut should_pop = false;
let mut returned_node = None;
let mut should_push = None;
while returned_node.is_none() {
if let Some((count, node)) = self.stack.last_mut() {
match node {
// We can only exit our looping when we get "real" nodes
VNode::Element(_) | VNode::Text(_) | VNode::Placeholder(_) => {
// We've recursed INTO an element/text
// We need to recurse *out* of it and move forward to the next
should_pop = true;
returned_node = Some(&**node);
}
// If we get a fragment we push the next child
VNode::Fragment(frag) => {
let count = *count as usize;
if count >= frag.children.len() {
should_pop = true;
} else {
should_push = Some(&frag.children[count]);
}
}
// For components, we load their root and push them onto the stack
VNode::Component(sc) => {
let scope = self.vdom.get_scope(sc.scope.get().unwrap()).unwrap();
// Simply swap the current node on the stack with the root of the component
*node = scope.root_node();
}
VNode::Template(_) => todo!(),
}
} else {
// If there's no more items on the stack, we're done!
return None;
}
if should_pop {
self.stack.pop();
if let Some((id, _)) = self.stack.last_mut() {
*id += 1;
}
should_pop = false;
}
if let Some(push) = should_push {
self.stack.push((0, push));
should_push = None;
}
}
returned_node
}
}
-752
View File
@@ -1,752 +0,0 @@
//! # Virtual DOM Implementation for Rust
//!
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
use crate::diff::DiffState;
use crate::innerlude::*;
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures_util::{future::poll_fn, StreamExt};
use fxhash::FxHashSet;
use indexmap::IndexSet;
use std::{collections::VecDeque, iter::FromIterator, task::Poll};
/// A virtual node system that progresses user events and diffs UI trees.
///
/// ## Guide
///
/// Components are defined as simple functions that take [`Scope`] and return an [`Element`].
///
/// ```rust, ignore
/// #[derive(Props, PartialEq)]
/// struct AppProps {
/// title: String
/// }
///
/// fn App(cx: Scope<AppProps>) -> Element {
/// cx.render(rsx!(
/// div {"hello, {cx.props.title}"}
/// ))
/// }
/// ```
///
/// Components may be composed to make complex apps.
///
/// ```rust, ignore
/// fn App(cx: Scope<AppProps>) -> Element {
/// cx.render(rsx!(
/// NavBar { routes: ROUTES }
/// Title { "{cx.props.title}" }
/// Footer {}
/// ))
/// }
/// ```
///
/// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
/// draw the UI.
///
/// ```rust, ignore
/// let mut vdom = VirtualDom::new(App);
/// let edits = vdom.rebuild();
/// ```
///
/// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler.
///
/// ```rust, ignore
/// let channel = vdom.get_scheduler_channel();
/// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent {
/// // ...
/// }))
/// ```
///
/// While waiting for UserEvents to occur, 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
/// 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);
/// }
/// ```
///
/// ## 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 {
/// cx.render(rsx!{
/// div { "Hello World" }
/// })
/// }
///
/// async fn main() {
/// let mut dom = VirtualDom::new(App);
///
/// let mut inital_edits = dom.rebuild();
/// apply_edits(inital_edits);
///
/// 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);
/// }
/// }
/// ```
pub struct VirtualDom {
scopes: ScopeArena,
pending_messages: VecDeque<SchedulerMsg>,
dirty_scopes: IndexSet<ScopeId>,
channel: (
UnboundedSender<SchedulerMsg>,
UnboundedReceiver<SchedulerMsg>,
),
}
/// The type of message that can be sent to the scheduler.
///
/// These messages control how the scheduler will process updates to the UI.
#[derive(Debug)]
pub enum SchedulerMsg {
/// Events from the Renderer
Event(UserEvent),
/// Immediate updates from Components that mark them as dirty
Immediate(ScopeId),
/// Mark all components as dirty and update them
DirtyAll,
/// New tasks from components that should be polled when the next poll is ready
NewTask(ScopeId),
}
// Methods to create the VirtualDom
impl VirtualDom {
/// Create a new VirtualDom with a component that does not have special props.
///
/// # Description
///
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
///
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
/// to toss out the entire tree.
///
///
/// # Example
/// ```rust, ignore
/// fn Example(cx: Scope) -> Element {
/// cx.render(rsx!( div { "hello world" } ))
/// }
///
/// let dom = VirtualDom::new(Example);
/// ```
///
/// Note: the VirtualDom is not progressed, you must either "run_with_deadline" or use "rebuild" to progress it.
pub fn new(root: Component) -> Self {
Self::new_with_props(root, ())
}
/// Create a new VirtualDom with the given properties for the root component.
///
/// # Description
///
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
///
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
/// to toss out the entire tree.
///
///
/// # Example
/// ```rust, ignore
/// #[derive(PartialEq, Props)]
/// struct SomeProps {
/// name: &'static str
/// }
///
/// fn Example(cx: Scope<SomeProps>) -> Element {
/// cx.render(rsx!{ div{ "hello {cx.props.name}" } })
/// }
///
/// let dom = VirtualDom::new(Example);
/// ```
///
/// Note: the VirtualDom is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
///
/// ```rust, ignore
/// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
/// let mutations = dom.rebuild();
/// ```
pub fn new_with_props<P>(root: Component<P>, root_props: P) -> Self
where
P: 'static,
{
Self::new_with_props_and_scheduler(
root,
root_props,
futures_channel::mpsc::unbounded::<SchedulerMsg>(),
)
}
/// 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: Component<P>,
root_props: P,
channel: (
UnboundedSender<SchedulerMsg>,
UnboundedReceiver<SchedulerMsg>,
),
) -> Self {
let scopes = ScopeArena::new(channel.0.clone());
scopes.new_with_key(
root as ComponentPtr,
Box::new(VComponentProps {
props: root_props,
memo: |_a, _b| unreachable!("memo on root will neve be run"),
render_fn: root,
}),
None,
ElementId(0),
0,
);
Self {
scopes,
channel,
dirty_scopes: IndexSet::from_iter([ScopeId(0)]),
pending_messages: VecDeque::new(),
}
}
/// Get the [`Scope`] for the root component.
///
/// This is useful for traversing the tree from the root for heuristics or alternative renderers that use Dioxus
/// directly.
///
/// This method is equivalent to calling `get_scope(ScopeId(0))`
///
/// # Example
///
/// ```rust, ignore
/// let mut dom = VirtualDom::new(example);
/// dom.rebuild();
///
///
/// ```
pub fn base_scope(&self) -> &ScopeState {
self.get_scope(ScopeId(0)).unwrap()
}
/// Get the [`ScopeState`] for a component given its [`ScopeId`]
///
/// # Example
///
///
///
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
self.scopes.get_scope(id)
}
/// Get an [`UnboundedSender`] handle to the channel used by the scheduler.
///
/// # Example
///
/// ```rust, ignore
/// let dom = VirtualDom::new(App);
/// let sender = dom.get_scheduler_channel();
/// ```
pub fn get_scheduler_channel(&self) -> UnboundedSender<SchedulerMsg> {
self.channel.0.clone()
}
/// Try to get an element from its ElementId
pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
self.scopes.get_element(id)
}
/// Add a new message to the scheduler queue directly.
///
///
/// This method makes it possible to send messages to the scheduler from outside the VirtualDom without having to
/// call `get_schedule_channel` and then `send`.
///
/// # Example
/// ```rust, ignore
/// let dom = VirtualDom::new(App);
/// dom.handle_message(SchedulerMsg::Immediate(ScopeId(0)));
/// ```
pub fn handle_message(&mut self, msg: SchedulerMsg) {
if self.channel.0.unbounded_send(msg).is_ok() {
self.process_all_messages();
}
}
/// Check if the [`VirtualDom`] has any pending updates or work to be done.
///
/// # Example
///
/// ```rust, ignore
/// let dom = VirtualDom::new(App);
///
/// // the dom is "dirty" when it is started and must be rebuilt to get the first render
/// assert!(dom.has_any_work());
/// ```
pub fn has_work(&self) -> bool {
!(self.dirty_scopes.is_empty() && self.pending_messages.is_empty())
}
/// Wait for the scheduler to have any work.
///
/// This method polls the internal future queue *and* the scheduler channel.
/// To add work to the VirtualDom, insert a message via the scheduler channel.
///
/// This lets us poll async tasks during idle periods without blocking the main thread.
///
/// # Example
///
/// ```rust, ignore
/// let dom = VirtualDom::new(App);
/// let sender = dom.get_scheduler_channel();
/// ```
pub async fn wait_for_work(&mut self) {
loop {
if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() {
break;
}
if self.pending_messages.is_empty() {
if self.scopes.tasks.has_tasks() {
use futures_util::future::{select, Either};
let scopes = &mut self.scopes;
let task_poll = poll_fn(|cx| {
let mut tasks = scopes.tasks.tasks.borrow_mut();
tasks.retain(|_, task| task.as_mut().poll(cx).is_pending());
match tasks.is_empty() {
true => Poll::Ready(()),
false => Poll::Pending,
}
});
match select(task_poll, self.channel.1.next()).await {
Either::Left((_, _)) => {}
Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
}
} else {
self.pending_messages
.push_front(self.channel.1.next().await.unwrap());
}
}
// Move all the messages into the queue
self.process_all_messages();
}
}
/// Manually kick the VirtualDom to process any
pub fn process_all_messages(&mut self) {
// clear out the scheduler queue
while let Ok(Some(msg)) = self.channel.1.try_next() {
self.pending_messages.push_front(msg);
}
// process all the messages pulled from the queue
while let Some(msg) = self.pending_messages.pop_back() {
self.process_message(msg);
}
}
/// Handle an individual message for the scheduler.
///
/// This will either call an event listener or mark a component as dirty.
pub fn process_message(&mut self, msg: SchedulerMsg) {
match msg {
SchedulerMsg::NewTask(_id) => {
// uh, not sure? I think end up re-polling it anyways
}
SchedulerMsg::Event(event) => {
if let Some(element) = event.element {
self.scopes.call_listener_with_bubbling(&event, element);
}
}
SchedulerMsg::Immediate(s) => {
self.dirty_scopes.insert(s);
}
SchedulerMsg::DirtyAll => {
for id in self.scopes.scopes.borrow().keys() {
self.dirty_scopes.insert(*id);
}
}
}
}
/// Run the virtualdom with a deadline.
///
/// This method will perform any outstanding diffing work and try to return as many mutations as possible before the
/// deadline is reached. This method accepts a closure that returns `true` if the deadline has been reached. To wrap
/// your future into a deadline, consider the `now_or_never` method from `future_utils`.
///
/// ```rust, ignore
/// let mut vdom = VirtualDom::new(App);
///
/// let timeout = TimeoutFuture::from_ms(16);
/// let deadline = || (&mut timeout).now_or_never();
///
/// let mutations = vdom.work_with_deadline(deadline);
/// ```
///
/// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
/// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
///
/// If the work is not finished by the deadline, Dioxus will store it for later and return when work_with_deadline
/// is called again. This means you can ensure some level of free time on the VirtualDom's thread during the work phase.
///
/// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
/// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
/// entirely jank-free applications that perform a ton of work.
///
/// In general use, Dioxus is plenty fast enough to not need to worry about this.
///
/// # Example
///
/// ```rust, ignore
/// fn App(cx: Scope) -> Element {
/// cx.render(rsx!( div {"hello"} ))
/// }
///
/// let mut dom = VirtualDom::new(App);
///
/// loop {
/// let mut timeout = TimeoutFuture::from_ms(16);
/// let deadline = move || (&mut timeout).now_or_never();
///
/// let mutations = dom.run_with_deadline(deadline).await;
///
/// apply_mutations(mutations);
/// }
/// ```
pub fn work_with_deadline<'a>(
&'a mut self,
renderer: &mut impl Renderer<'a>,
mut deadline: impl FnMut() -> bool,
) {
while !self.dirty_scopes.is_empty() {
let scopes = &self.scopes;
let mut diff_state = DiffState::new(scopes, renderer);
let mut ran_scopes = FxHashSet::default();
// Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
self.dirty_scopes
.retain(|id| scopes.get_scope(*id).is_some());
self.dirty_scopes.sort_by(|a, b| {
let h1 = scopes.get_scope(*a).unwrap().height;
let h2 = scopes.get_scope(*b).unwrap().height;
h1.cmp(&h2).reverse()
});
if let Some(scopeid) = self.dirty_scopes.pop() {
if !ran_scopes.contains(&scopeid) {
ran_scopes.insert(scopeid);
self.scopes.run_scope(scopeid);
diff_state.diff_scope(scopeid);
let DiffState { mutations, .. } = diff_state;
todo!()
// for scope in &mutations.dirty_scopes {
// self.dirty_scopes.remove(scope);
// }
// if !mutations.edits.is_empty() {
// committed_mutations.push(mutations);
// }
// todo: pause the diff machine
// if diff_state.work(&mut deadline) {
// let DiffState { mutations, .. } = diff_state;
// for scope in &mutations.dirty_scopes {
// self.dirty_scopes.remove(scope);
// }
// committed_mutations.push(mutations);
// } else {
// // leave the work in an incomplete state
// //
// // todo: we should store the edits and re-apply them later
// // for now, we just dump the work completely (threadsafe)
// return committed_mutations;
// }
}
}
}
}
/// Run the virtualdom, waiting for all async components to finish rendering
///
/// As they finish rendering, the virtualdom will apply the mutations to the renderer.
pub async fn render(&mut self, renderer: &mut impl Renderer<'_>) {
//
}
/// 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.
///
/// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
/// root component will be ran once and then diffed. All updates will flow out as mutations.
///
/// All state stored in components will be completely wiped away.
///
/// # Example
/// ```rust, ignore
/// static App: Component = |cx| cx.render(rsx!{ "hello world" });
/// let mut dom = VirtualDom::new();
/// let edits = dom.rebuild();
///
/// apply_edits(edits);
/// ```
pub fn rebuild<'a>(&'a mut self, dom: &mut impl Renderer<'a>) {
let scope_id = ScopeId(0);
let mut diff_state = DiffState::new(&self.scopes, dom);
self.scopes.run_scope(scope_id);
diff_state.element_stack.push(ElementId(0));
diff_state.scope_stack.push(scope_id);
let node = self.scopes.fin_head(scope_id);
let created = diff_state.create_node(node);
diff_state.mutations.append_children(created as u32);
self.dirty_scopes.clear();
assert!(self.dirty_scopes.is_empty());
}
/// Compute a manual diff of the VirtualDom between states.
///
/// This can be useful when state inside the DOM is remotely changed from the outside, but not propagated as an event.
///
/// In this case, every component will be diffed, even if their props are memoized. This method is intended to be used
/// to force an update of the DOM when the state of the app is changed outside of the app.
///
/// To force a reflow of the entire VirtualDom, use `ScopeId(0)` as the scope_id.
///
/// # Example
/// ```rust, ignore
/// #[derive(PartialEq, Props)]
/// struct AppProps {
/// value: Shared<&'static str>,
/// }
///
/// static App: Component<AppProps> = |cx| {
/// let val = cx.value.borrow();
/// cx.render(rsx! { div { "{val}" } })
/// };
///
/// let value = Rc::new(RefCell::new("Hello"));
/// let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone(), });
///
/// let _ = dom.rebuild();
///
/// *value.borrow_mut() = "goodbye";
///
/// let edits = dom.hard_diff(ScopeId(0));
/// ```
pub fn hard_diff<'a>(&'a mut self, scope_id: ScopeId, dom: &mut impl Renderer<'a>) {
let mut diff_machine = DiffState::new(&self.scopes, dom);
self.scopes.run_scope(scope_id);
let (old, new) = (
diff_machine.scopes.wip_head(scope_id),
diff_machine.scopes.fin_head(scope_id),
);
diff_machine.force_diff = true;
diff_machine.scope_stack.push(scope_id);
let scope = diff_machine.scopes.get_scope(scope_id).unwrap();
diff_machine.element_stack.push(scope.container);
diff_machine.diff_node(old, new);
}
// /// Renders an `rsx` call into the Base Scope's allocator.
// ///
// /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
// ///
// /// ```rust, ignore
// /// fn Base(cx: Scope) -> Element {
// /// render!(div {})
// /// }
// ///
// /// let dom = VirtualDom::new(Base);
// /// let nodes = dom.render_nodes(rsx!("div"));
// /// ```
// 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::new(scope);
// let node = lazy_nodes.call(factory);
// frame.bump.alloc(node)
// }
// /// Renders an `rsx` call into the Base Scope's allocator.
// ///
// /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
// ///
// /// ```rust, ignore
// /// fn Base(cx: Scope) -> Element {
// /// render!(div {})
// /// }
// ///
// /// let dom = VirtualDom::new(Base);
// /// let nodes = dom.render_nodes(rsx!("div"));
// /// ```
// pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
// let mut machine = DiffState::new(&self.scopes);
// machine.element_stack.push(ElementId(0));
// machine.scope_stack.push(ScopeId(0));
// machine.diff_node(old, new);
// machine.mutations
// }
// /// Renders an `rsx` call into the Base Scope's allocator.
// ///
// /// Useful when needing to render nodes from outside the VirtualDom, such as in a test.
// ///
// ///
// /// ```rust, ignore
// /// fn Base(cx: Scope) -> Element {
// /// render!(div {})
// /// }
// ///
// /// let dom = VirtualDom::new(Base);
// /// let nodes = dom.render_nodes(rsx!("div"));
// /// ```
// pub fn create_vnodes<'a>(&'a self, nodes: LazyNodes<'a, '_>) -> Mutations<'a> {
// let mut machine = DiffState::new(&self.scopes);
// machine.scope_stack.push(ScopeId(0));
// machine.element_stack.push(ElementId(0));
// let node = self.render_vnodes(nodes);
// let created = machine.create_node(node);
// machine.mutations.append_children(created as u32);
// machine.mutations
// }
// /// Renders an `rsx` call into the Base Scopes's arena.
// ///
// /// Useful when needing to diff two rsx! calls from outside the VirtualDom, such as in a test.
// ///
// ///
// /// ```rust, ignore
// /// fn Base(cx: Scope) -> Element {
// /// render!(div {})
// /// }
// ///
// /// let dom = VirtualDom::new(Base);
// /// let nodes = dom.render_nodes(rsx!("div"));
// /// ```
// pub fn diff_lazynodes<'a>(
// &'a self,
// left: LazyNodes<'a, '_>,
// right: LazyNodes<'a, '_>,
// ) -> (Mutations<'a>, Mutations<'a>) {
// let (old, new) = (self.render_vnodes(left), self.render_vnodes(right));
// let mut create = DiffState::new(&self.scopes);
// create.scope_stack.push(ScopeId(0));
// create.element_stack.push(ElementId(0));
// let created = create.create_node(old);
// create.mutations.append_children(created as u32);
// let mut edit = DiffState::new(&self.scopes);
// edit.scope_stack.push(ScopeId(0));
// edit.element_stack.push(ElementId(0));
// edit.diff_node(old, new);
// (create.mutations, edit.mutations)
// }
}
/*
Scopes and ScopeArenas are never dropped internally.
An app will always occupy as much memory as its biggest form.
This means we need to handle all specifics of drop *here*. It's easier
to reason about centralizing all the drop logic in one spot rather than scattered in each module.
Broadly speaking, we want to use the remove_nodes method to clean up *everything*
This will drop listeners, borrowed props, and hooks for all components.
We need to do this in the correct order - nodes at the very bottom must be dropped first to release
the borrow chain.
Once the contents of the tree have been cleaned up, we can finally clean up the
memory used by ScopeState itself.
questions:
should we build a vcomponent for the root?
- probably - yes?
- store the vcomponent in the root dom
- 1: Use remove_nodes to use the ensure_drop_safety pathway to safely drop the tree
- 2: Drop the ScopeState itself
*/
impl Drop for VirtualDom {
fn drop(&mut self) {
// the best way to drop the dom is to replace the root scope with a dud
// the diff infrastructure will then finish the rest
let scope = self.scopes.get_scope(ScopeId(0)).unwrap();
// todo: move the remove nodes method onto scopearena
// this will clear *all* scopes *except* the root scope
// let mut machine = DiffState::new(&self.scopes);
// machine.remove_nodes([scope.root_node()], false);
todo!("drop the root scope without leaking anything");
// Now, clean up the root scope
// safety: there are no more references to the root scope
let scope = unsafe { &mut *self.scopes.get_scope_raw(ScopeId(0)).unwrap() };
scope.reset();
// make sure there are no "live" components
for (_, scopeptr) in self.scopes.scopes.get_mut().drain() {
// safety: all scopes were made in the bump's allocator
// They are never dropped until now. The only way to drop is through Box.
let scope = unsafe { bumpalo::boxed::Box::from_raw(scopeptr) };
drop(scope);
}
for scopeptr in self.scopes.free_scopes.get_mut().drain(..) {
// safety: all scopes were made in the bump's allocator
// They are never dropped until now. The only way to drop is through Box.
let mut scope = unsafe { bumpalo::boxed::Box::from_raw(scopeptr) };
scope.reset();
drop(scope);
}
}
}
+26 -9
View File
@@ -7,20 +7,33 @@ use crate::{
Element,
};
pub trait AnyProps<'a> {
fn as_ptr(&self) -> *const ();
/// A trait that essentially allows VComponentProps to be used generically
pub unsafe trait AnyProps<'a> {
fn props_ptr(&self) -> *const ();
fn render(&'a self, bump: &'a ScopeState) -> RenderReturn<'a>;
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool;
}
pub(crate) struct VComponentProps<'a, P, A, F: ComponentReturn<'a, A> = Element<'a>> {
pub(crate) struct VProps<'a, P, A, F: ComponentReturn<'a, A> = Element<'a>> {
pub render_fn: fn(Scope<'a, P>) -> F,
pub memo: unsafe fn(&P, &P) -> bool,
pub props: P,
pub _marker: PhantomData<A>,
// pub props: PropsAllocation<P>,
_marker: PhantomData<A>,
}
impl<'a, P, A, F: ComponentReturn<'a, A>> VComponentProps<'a, P, A, F> {
enum PropsAllocation<P> {
/// If it's memoized, then the heap owns the props
Memoized(*mut P),
/// If it's borrowed, then the parent owns the props
Borrowed(P),
}
impl<'a, P, A, F> VProps<'a, P, A, F>
where
F: ComponentReturn<'a, A>,
{
pub(crate) fn new(
render_fn: fn(Scope<'a, P>) -> F,
memo: unsafe fn(&P, &P) -> bool,
@@ -30,13 +43,17 @@ impl<'a, P, A, F: ComponentReturn<'a, A>> VComponentProps<'a, P, A, F> {
render_fn,
memo,
props,
// props: PropsAllocation::Borrowed(props),
_marker: PhantomData,
}
}
}
impl<'a, P, A, F: ComponentReturn<'a, A>> AnyProps<'a> for VComponentProps<'a, P, A, F> {
fn as_ptr(&self) -> *const () {
unsafe impl<'a, P, A, F> AnyProps<'a> for VProps<'a, P, A, F>
where
F: ComponentReturn<'a, A>,
{
fn props_ptr(&self) -> *const () {
&self.props as *const _ as *const ()
}
@@ -45,8 +62,8 @@ impl<'a, P, A, F: ComponentReturn<'a, A>> AnyProps<'a> for VComponentProps<'a, P
// you *must* make this check *before* calling this method
// if your functions are not the same, then you will downcast a pointer into a different type (UB)
unsafe fn memoize(&self, other: &dyn AnyProps) -> bool {
let real_other: &P = &*(other.as_ptr() as *const _ as *const P);
let real_us: &P = &*(self.as_ptr() as *const _ as *const P);
let real_other: &P = &*(other.props_ptr() as *const _ as *const P);
let real_us: &P = &*(self.props_ptr() as *const _ as *const P);
(self.memo)(real_us, real_other)
}
+6 -9
View File
@@ -70,11 +70,7 @@ impl VirtualDom {
&template.dynamic_nodes[*id],
*id,
),
DynamicNode::Text(VText {
id: slot,
value,
inner,
}) => {
DynamicNode::Text(VText { id: slot, value }) => {
let id = self.next_element(template, template.template.node_paths[*id]);
slot.set(id);
mutations.push(CreateTextNode { value, id });
@@ -239,7 +235,7 @@ impl VirtualDom {
idx: usize,
) -> usize {
match &node {
DynamicNode::Text(VText { id, value, inner }) => {
DynamicNode::Text(VText { id, value }) => {
let new_id = self.next_element(template, template.template.node_paths[idx]);
id.set(new_id);
mutations.push(HydrateText {
@@ -251,9 +247,10 @@ impl VirtualDom {
}
DynamicNode::Component(component) => {
let scope = self
.new_scope(unsafe { std::mem::transmute(component.props.get()) })
.id;
let props = component.props.replace(None).unwrap();
let prop_ptr = unsafe { std::mem::transmute(props.as_ref()) };
let scope = self.new_scope(prop_ptr).id;
component.props.replace(Some(props));
component.scope.set(Some(scope));
+123 -83
View File
@@ -5,7 +5,8 @@ use crate::innerlude::{Mutations, VComponent, VFragment, VText};
use crate::virtual_dom::VirtualDom;
use crate::{Attribute, AttributeValue, TemplateNode};
use crate::any_props::VComponentProps;
use crate::any_props::VProps;
use DynamicNode::*;
use crate::mutations::Mutation;
use crate::nodes::{DynamicNode, Template, TemplateId};
@@ -77,7 +78,7 @@ impl<'b> VirtualDom {
use RenderReturn::{Async, Sync};
match (left, right) {
// diff
(Sync(Some(l)), Sync(Some(r))) => self.diff_node(m, l, r),
(Sync(Some(l)), Sync(Some(r))) => self.diff_vnode(m, l, r),
// remove old with placeholder
(Sync(Some(l)), Sync(None)) | (Sync(Some(l)), Async(_)) => {
@@ -91,7 +92,7 @@ impl<'b> VirtualDom {
(Sync(None), Sync(Some(_))) => {}
(Async(_), Sync(Some(v))) => {}
// nothing...
// nothing... just transfer the placeholders over
(Async(_), Async(_))
| (Sync(None), Sync(None))
| (Sync(None), Async(_))
@@ -99,23 +100,20 @@ impl<'b> VirtualDom {
}
}
pub fn diff_node(
pub fn diff_vnode(
&mut self,
muts: &mut Mutations<'b>,
left_template: &'b VNode<'b>,
right_template: &'b VNode<'b>,
) {
if left_template.template.id != right_template.template.id {
// do a light diff of the roots nodes.
todo!("lightly diff roots and replace");
return;
return self.light_diff_templates(muts, left_template, right_template);
}
for (_idx, (left_attr, right_attr)) in left_template
for (left_attr, right_attr) in left_template
.dynamic_attrs
.iter()
.zip(right_template.dynamic_attrs.iter())
.enumerate()
{
// Move over the ID from the old to the new
right_attr
@@ -135,67 +133,76 @@ impl<'b> VirtualDom {
}
}
for (idx, (left_node, right_node)) in left_template
for (left_node, right_node) in left_template
.dynamic_nodes
.iter()
.zip(right_template.dynamic_nodes.iter())
.enumerate()
{
use DynamicNode::*;
match (left_node, right_node) {
(Component(left), Component(right)) => self.diff_vcomponent(muts, left, right),
(Text(left), Text(right)) => self.diff_vtext(muts, left, right),
(Fragment(left), Fragment(right)) => self.diff_vfragment(muts, left, right),
(Placeholder(_), Placeholder(_)) => todo!(),
// Make sure to drop all the fragment children properly
(DynamicNode::Fragment { .. }, right) => todo!(),
(Text(left), right) => {
todo!()
// let m = self.create_dynamic_node(muts, right_template, right, idx);
// muts.push(Mutation::ReplaceWith { id: lid.get(), m });
}
(Placeholder(_), _) => todo!(),
// Make sure to drop the component properly
(Component { .. }, right) => {
// remove all the component roots except for the first
// replace the first with the new node
let m = self.create_dynamic_node(muts, right_template, right, idx);
todo!()
}
(Placeholder(left), Placeholder(right)) => right.set(left.get()),
_ => self.replace(muts, left_template, right_template, left_node, right_node),
};
}
}
fn replace(
&mut self,
muts: &mut Mutations<'b>,
left_template: &'b VNode<'b>,
right_template: &'b VNode<'b>,
left: &'b DynamicNode<'b>,
right: &'b DynamicNode<'b>,
) {
}
fn diff_vcomponent(
&mut self,
muts: &mut Mutations<'b>,
left: &'b VComponent<'b>,
right: &'b VComponent<'b>,
) {
let left_props = unsafe { &mut *left.props.get() };
let right_props = unsafe { &mut *right.props.get() };
// Due to how templates work, we should never get two different components. The only way we could enter
// this codepath is through "light_diff", but we check there that the pointers are the same
assert_eq!(left.render_fn, right.render_fn);
// Ensure these two props are of the same component type
match left_props.as_ptr() == right_props.as_ptr() {
true => {
//
if left.static_props {
let props_are_same = unsafe { left_props.memoize(right_props) };
/*
if props_are_same {
//
} else {
//
}
} else {
}
}
false => todo!(),
let left = rsx!{ Component {} }
let right = rsx!{ Component {} }
*/
// Make sure the new vcomponent has the right scopeid associated to it
let scope_id = left.scope.get().unwrap();
right.scope.set(Some(scope_id));
// copy out the box for both
let old = left.props.replace(None).unwrap();
let new = right.props.replace(None).unwrap();
// If the props are static, then we try to memoize by setting the new with the old
// The target scopestate still has the reference to the old props, so there's no need to update anything
// This also implicitly drops the new props since they're not used
if left.static_props && unsafe { old.memoize(new.as_ref()) } {
return right.props.set(Some(old));
}
// If the props are dynamic *or* the memoization failed, then we need to diff the props
// First, move over the props from the old to the new, dropping old props in the process
self.scopes[scope_id.0].props = unsafe { std::mem::transmute(new.as_ref()) };
right.props.set(Some(new));
// Now run the component and diff it
self.run_scope(scope_id);
self.diff_scope(muts, scope_id);
}
/// Lightly diff the two templates, checking only their roots.
@@ -236,44 +243,23 @@ impl<'b> VirtualDom {
/// Component { ..props }
/// }
/// ```
fn light_diff_tempaltes(
fn light_diff_templates(
&mut self,
muts: &mut Mutations<'b>,
left: &'b VNode<'b>,
right: &'b VNode<'b>,
) -> bool {
if left.template.roots.len() != right.template.roots.len() {
return false;
) {
if let Some(components) = matching_components(left, right) {
components
.into_iter()
.for_each(|(l, r)| self.diff_vcomponent(muts, l, r))
}
let mut components = vec![];
// run through the components, ensuring they're the same
for (l, r) in left.template.roots.iter().zip(right.template.roots.iter()) {
match (l, r) {
(TemplateNode::Dynamic(l), TemplateNode::Dynamic(r)) => {
components.push(match (&left.dynamic_nodes[*l], &right.dynamic_nodes[*r]) {
(DynamicNode::Component(l), DynamicNode::Component(r)) => {
let l_props = unsafe { &*l.props.get() };
let r_props = unsafe { &*r.props.get() };
(l, r)
}
_ => return false,
})
}
_ => return false,
}
}
// run through the components, diffing them
for (l, r) in components {
self.diff_vcomponent(muts, l, r);
}
true
}
/// Diff the two text nodes
///
/// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's
/// different.
fn diff_vtext(&mut self, muts: &mut Mutations<'b>, left: &'b VText<'b>, right: &'b VText<'b>) {
right.id.set(left.id.get());
if left.value != right.value {
@@ -297,6 +283,7 @@ impl<'b> VirtualDom {
// todo!()
// }
// (_, []) => {
// // if this fragment is the only child of its parent, then we can use the "RemoveAllChildren" mutation
// todo!()
// }
// _ => {
@@ -319,8 +306,6 @@ impl<'b> VirtualDom {
// }
// }
// }
todo!()
}
// Diff children that are not keyed.
@@ -351,7 +336,7 @@ impl<'b> VirtualDom {
}
for (new, old) in new.iter().zip(old.iter()) {
self.diff_node(muts, old, new);
self.diff_vnode(muts, old, new);
}
}
@@ -693,3 +678,58 @@ impl<'b> VirtualDom {
//
}
}
fn matching_components<'a>(
left: &'a VNode<'a>,
right: &'a VNode<'a>,
) -> Option<Vec<(&'a VComponent<'a>, &'a VComponent<'a>)>> {
if left.template.roots.len() != right.template.roots.len() {
return None;
}
// run through the components, ensuring they're the same
left.template
.roots
.iter()
.zip(right.template.roots.iter())
.map(|(l, r)| {
let (l, r) = match (l, r) {
(TemplateNode::Dynamic(l), TemplateNode::Dynamic(r)) => (l, r),
_ => return None,
};
let (l, r) = match (&left.dynamic_nodes[*l], &right.dynamic_nodes[*r]) {
(Component(l), Component(r)) => (l, r),
_ => return None,
};
(l.render_fn == r.render_fn).then(|| (l, r))
})
.collect()
}
/// We can apply various optimizations to dynamic nodes that are the single child of their parent.
///
/// IE
/// - for text - we can use SetTextContent
/// - for clearning children we can use RemoveChildren
/// - for appending children we can use AppendChildren
fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
let path = node.template.node_paths[idx];
// use a loop to index every static node's children until the path has run out
// only break if the last path index is a dynamic node
let mut static_node = &node.template.roots[path[0] as usize];
for i in 1..path.len() - 1 {
match static_node {
TemplateNode::Element { children, .. } => static_node = &children[path[i] as usize],
_ => return false,
}
}
match static_node {
TemplateNode::Element { children, .. } => children.len() == 1,
_ => false,
}
}
+9 -11
View File
@@ -1,4 +1,5 @@
use std::{
any::Any,
cell::{Cell, RefCell},
fmt::Arguments,
};
@@ -8,7 +9,7 @@ use bumpalo::Bump;
use std::future::Future;
use crate::{
any_props::{AnyProps, VComponentProps},
any_props::{AnyProps, VProps},
arena::ElementId,
innerlude::{DynamicNode, EventHandler, VComponent, VFragment, VText},
Attribute, AttributeValue, Element, LazyNodes, Properties, Scope, ScopeState, VNode,
@@ -21,7 +22,6 @@ impl ScopeState {
DynamicNode::Text(VText {
id: Cell::new(ElementId(0)),
value: text,
inner: false,
})
}
@@ -76,10 +76,11 @@ impl ScopeState {
P: Properties + 'a,
{
let as_component = component;
let vcomp = VComponentProps::new(as_component, P::memoize, props);
let as_dyn = self.bump().alloc(vcomp) as &mut dyn AnyProps;
let detached_dyn: *mut dyn AnyProps = unsafe { std::mem::transmute(as_dyn) };
let vcomp = VProps::new(as_component, P::memoize, props);
let as_dyn: Box<dyn AnyProps<'a>> = Box::new(vcomp);
let extended: Box<dyn AnyProps> = unsafe { std::mem::transmute(as_dyn) };
// let as_dyn: &dyn AnyProps = self.bump().alloc(vcomp);
// todo: clean up borrowed props
// if !P::IS_STATIC {
// let vcomp = &*vcomp;
@@ -89,8 +90,9 @@ impl ScopeState {
DynamicNode::Component(VComponent {
name: fn_name,
render_fn: component as *const (),
static_props: P::IS_STATIC,
props: Cell::new(detached_dyn),
props: Cell::new(Some(extended)),
placeholder: Cell::new(None),
scope: Cell::new(None),
})
@@ -178,7 +180,6 @@ impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
DynamicNode::Fragment(VFragment {
nodes: cx.bump().alloc([self.call(cx)]),
inner: false,
})
}
}
@@ -248,10 +249,7 @@ where
match children.len() {
0 => DynamicNode::Placeholder(Cell::new(ElementId(0))),
_ => DynamicNode::Fragment(VFragment {
inner: false,
nodes: children,
}),
_ => DynamicNode::Fragment(VFragment { nodes: children }),
}
}
}
+13 -55
View File
@@ -131,26 +131,35 @@ impl<'a> DynamicNode<'a> {
}
}
#[derive(Debug)]
pub struct VComponent<'a> {
pub name: &'static str,
pub static_props: bool,
pub props: Cell<*mut dyn AnyProps<'a>>,
pub placeholder: Cell<Option<ElementId>>,
pub scope: Cell<Option<ScopeId>>,
pub props: Cell<Option<Box<dyn AnyProps<'a> + 'a>>>,
pub render_fn: *const (),
}
impl<'a> std::fmt::Debug for VComponent<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("VComponent")
.field("name", &self.name)
.field("static_props", &self.static_props)
.field("placeholder", &self.placeholder)
.field("scope", &self.scope)
.finish()
}
}
#[derive(Debug)]
pub struct VText<'a> {
pub id: Cell<ElementId>,
pub value: &'a str,
pub inner: bool,
}
#[derive(Debug)]
pub struct VFragment<'a> {
pub nodes: &'a [VNode<'a>],
pub inner: bool,
}
#[derive(Debug)]
@@ -268,54 +277,3 @@ fn what_are_the_sizes() {
dbg!(std::mem::size_of::<Template>());
dbg!(std::mem::size_of::<TemplateNode>());
}
/*
SSR includes data-id which allows O(1) hydration
we read the edit stream dn then we can just rehydare
ideas:
- IDs for lookup
- use edit stream to hydrate
- write comments to dom that specify size of children
IDs for lookups
- adds noise to generated html
- doesnt work for text nodes
- suspense could cause ordering to be weird
Names for lookups:
- label each root or something with the template name
- label each dynamic node with a path
- noisy too
- allows reverse lookups
Ideal:
- no noise in the dom
- fast, ideally O(1)
- able to pick apart text nodes that get merged during SSR
--> render vdom
--> traverse vdom and real dom simultaneously
IE
div {
div {
div {
"thing"
}
}
}
*/
+8 -1
View File
@@ -1,6 +1,7 @@
use crate::{
any_props::AnyProps,
bump_frame::BumpFrame,
diff::DirtyScope,
factory::RenderReturn,
innerlude::{SuspenseId, SuspenseLeaf},
scheduler::RcWake,
@@ -16,7 +17,7 @@ use std::{
};
impl VirtualDom {
pub(super) fn new_scope(&mut self, props: *mut dyn AnyProps<'static>) -> &mut ScopeState {
pub(super) fn new_scope(&mut self, props: *const dyn AnyProps<'static>) -> &mut ScopeState {
let parent = self.acquire_current_scope_raw();
let entry = self.scopes.vacant_entry();
let height = unsafe { parent.map(|f| (*f).height).unwrap_or(0) + 1 };
@@ -125,6 +126,12 @@ impl VirtualDom {
// And move the render generation forward by one
scope.render_cnt.set(scope.render_cnt.get() + 1);
// remove this scope from dirty scopes
self.dirty_scopes.remove(&DirtyScope {
height: scope.height,
id: scope.id,
});
// rebind the lifetime now that its stored internally
unsafe { mem::transmute(alloced) }
}
+2 -2
View File
@@ -3,7 +3,7 @@
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
use crate::{
any_props::VComponentProps,
any_props::VProps,
arena::ElementId,
arena::ElementRef,
diff::DirtyScope,
@@ -240,7 +240,7 @@ impl VirtualDom {
finished_fibers: Vec::new(),
};
let root = dom.new_scope(Box::into_raw(Box::new(VComponentProps::new(
let root = dom.new_scope(Box::leak(Box::new(VProps::new(
root,
|_, _| unreachable!(),
root_props,