Merge pull request #32 from DioxusLabs/jk/reworking_components

wip: rework components, safety, and tasks
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2021-11-07 22:47:03 -05:00
commit 0e6c845690
58 files changed
+4191 -3813

No files matched your search

+1 -1
View File
@@ -21,7 +21,7 @@ fn main() {
}
fn App((cx, props): Scope<()>) -> Element {
let text: &mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f, |_| {});
let text: &mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f);
let first = text.get_mut(0).unwrap();
+13 -11
View File
@@ -90,15 +90,17 @@ static App: FC<()> = |(cx, _)| {
}
};
rsx!(cx, body {
link {
rel: "stylesheet"
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css"
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5"
crossorigin: "anonymous"
}
margin_left: "35%"
h1 {"Dioxus CRM Example"}
{scene}
})
cx.render(rsx!(
body {
link {
rel: "stylesheet"
href: "https://unpkg.com/purecss@2.0.6/build/pure-min.css"
integrity: "sha384-Uu6IeWbM+gzNVXJcM9XV3SohHtmWE+3VGi496jvgX1jyvDTXfdK+rfZc8C1Aehk5"
crossorigin: "anonymous"
}
margin_left: "35%"
h1 {"Dioxus CRM Example"}
{scene}
}
))
};
+9 -5
View File
@@ -102,12 +102,9 @@ pub static Example: FC<()> = |(cx, props)| {
}}
// Matching
// Matching will throw a Rust error about "no two closures are the same type"
// To fix this, call "render" method or use the "in" syntax to produce VNodes.
// There's nothing we can do about it, sorry :/ (unless you want *really* unhygienic macros)
{match true {
true => rsx!(cx, h1 {"Top text"}),
false => cx.render(rsx!( h1 {"Bottom text"}))
true => rsx!( h1 {"Top text"}),
false => rsx!( h1 {"Bottom text"})
}}
// Conditional rendering
@@ -174,10 +171,17 @@ pub static Example: FC<()> = |(cx, props)| {
// Can take children too!
Taller { a: "asd", div {"hello world!"} }
// helper functions
{helper(cx, "hello world!")}
}
})
};
fn helper(cx: Context, text: &str) -> Element {
rsx!(cx, p { "{text}" })
}
mod baller {
use super::*;
#[derive(Props, PartialEq)]
+1 -1
View File
@@ -21,6 +21,6 @@ static App: FC<()> = |(cx, props)| {
struct MyProps<'a> {
text: &'a str,
}
fn App2<'a>(cx: Context<'a>, props: &'a MyProps) -> Element<'a> {
fn App2(cx: Context, props: &MyProps) -> Element {
None
}
+6 -4
View File
@@ -24,14 +24,16 @@ static App: FC<()> = |(cx, props)| {
.await
.unwrap();
},
|cx, props| {
|props| {
//
rsx!(WeatherDisplay {})
cx.render(rsx!(WeatherDisplay {}))
},
);
rsx!(cx, div {
{body}
cx.render(rsx! {
div {
{body}
}
})
};
+2 -2
View File
@@ -210,12 +210,12 @@ impl ToTokens for ElementAttrNamed {
ElementAttr::EventClosure { name, closure } => {
quote! {
dioxus::events::on::#name(__cx, #closure)
dioxus_elements::on::#name(__cx, #closure)
}
}
ElementAttr::EventTokens { name, tokens } => {
quote! {
dioxus::events::on::#name(__cx, #tokens)
dioxus_elements::on::#name(__cx, #tokens)
}
}
};
+1 -3
View File
@@ -40,8 +40,6 @@ indexmap = "1.7.0"
# Serialize the Edits for use in Webview/Liveview instances
serde = { version = "1", features = ["derive"], optional = true }
serde_repr = { version = "0.1.7", optional = true }
[dev-dependencies]
anyhow = "1.0.42"
dioxus-html = { path = "../html" }
@@ -54,7 +52,7 @@ dioxus-core-macro = { path = "../core-macro", version = "0.1.2" }
[features]
default = []
serialize = ["serde", "serde_repr"]
serialize = ["serde"]
debug_vdom = []
[[bench]]
+16
View File
@@ -40,3 +40,19 @@ We have big goals for Dioxus. The final implementation must:
- Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs.
- Be modular. Components and hooks should be work anywhere without worrying about target platform.
## Safety
Dioxus deals with arenas, lifetimes, asynchronous tasks, custom allocators, pinning, and a lot more foundational low-level work that is very difficult to implement with 0 unsafe.
If you don't want to use a crate that uses unsafe, then this crate is not for you.
however, we are always interested in decreasing the scope of the core VirtualDom to make it easier to review.
We'd also be happy to welcome PRs that can eliminate unsafe code while still upholding the numerous variants required to execute certain features.
There's a few invariants that are very important:
- References to `ScopeInner` and `Props` passed into components are *always* valid for as long as the component exists. Even if the scope backing is resized to fit more scopes, the scope has to stay the same place in memory.
+102
View File
@@ -99,3 +99,105 @@ Internally, the flow of suspense works like this:
9. diff that node with the new node with a low priority on its own fiber
10. return the patches back to the event loop
11. apply the patches to the real dom
/*
Welcome to Dioxus's cooperative, priority-based scheduler.
I hope you enjoy your stay.
Some essential reading:
- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L197-L200
- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L440
- https://github.com/WICG/is-input-pending
- https://web.dev/rail/
- https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react
# What's going on?
Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI
snappy and "jank free" even under heavy work loads. Dioxus already has the "speed" part figured out - but there's no
point in being "fast" if you can't also be "responsive."
As such, Dioxus can manually decide on what work is most important at any given moment in time. With a properly tuned
priority system, Dioxus can ensure that user interaction is prioritized and committed as soon as possible (sub 100ms).
The controller responsible for this priority management is called the "scheduler" and is responsible for juggling many
different types of work simultaneously.
# How does it work?
Per the RAIL guide, we want to make sure that A) inputs are handled ASAP and B) animations are not blocked.
React-three-fiber is a testament to how amazing this can be - a ThreeJS scene is threaded in between work periods of
React, and the UI still stays snappy!
While it's straightforward to run code ASAP and be as "fast as possible", what's not _not_ straightforward is how to do
this while not blocking the main thread. The current prevailing thought is to stop working periodically so the browser
has time to paint and run animations. When the browser is finished, we can step in and continue our work.
React-Fiber uses the "Fiber" concept to achieve a pause-resume functionality. This is worth reading up on, but not
necessary to understand what we're doing here. In Dioxus, our DiffMachine is guided by DiffInstructions - essentially
"commands" that guide the Diffing algorithm through the tree. Our "diff_scope" method is async - we can literally pause
our DiffMachine "mid-sentence" (so to speak) by just stopping the poll on the future. The DiffMachine periodically yields
so Rust's async machinery can take over, allowing us to customize when exactly to pause it.
React's "should_yield" method is more complex than ours, and I assume we'll move in that direction as Dioxus matures. For
now, Dioxus just assumes a TimeoutFuture, and selects! on both the Diff algorithm and timeout. If the DiffMachine finishes
before the timeout, then Dioxus will work on any pending work in the interim. If there is no pending work, then the changes
are committed, and coroutines are polled during the idle period. However, if the timeout expires, then the DiffMachine
future is paused and saved (self-referentially).
# Priority System
So far, we've been able to thread our Dioxus work between animation frames - the main thread is not blocked! But that
doesn't help us _under load_. How do we still stay snappy... even if we're doing a lot of work? Well, that's where
priorities come into play. The goal with priorities is to schedule shorter work as a "high" priority and longer work as
a "lower" priority. That way, we can interrupt long-running low-priority work with short-running high-priority work.
React's priority system is quite complex.
There are 5 levels of priority and 2 distinctions between UI events (discrete, continuous). I believe React really only
uses 3 priority levels and "idle" priority isn't used... Regardless, there's some batching going on.
For Dioxus, we're going with a 4 tier priority system:
- Sync: Things that need to be done by the next frame, like TextInput on controlled elements
- High: for events that block all others - clicks, keyboard, and hovers
- Medium: for UI events caused by the user but not directly - scrolls/forms/focus (all other events)
- Low: set_state called asynchronously, and anything generated by suspense
In "Sync" state, we abort our "idle wait" future, and resolve the sync queue immediately and escape. Because we completed
work before the next rAF, any edits can be immediately processed before the frame ends. Generally though, we want to leave
as much time to rAF as possible. "Sync" is currently only used by onInput - we'll leave some docs telling people not to
do anything too arduous from onInput.
For the rest, we defer to the rIC period and work down each queue from high to low.
*/
Strategy:
- When called, check for any UI events that might've been received since the last frame.
- Dump all UI events into a "pending discrete" queue and a "pending continuous" queue.
- If there are any pending discrete events, then elevate our priority level. If our priority level is already "high,"
then we need to finish the high priority work first. If the current work is "low" then analyze what scopes
will be invalidated by this new work. If this interferes with any in-flight medium or low work, then we need
to bump the other work out of the way, or choose to process it so we don't have any conflicts.
'static components have a leg up here since their work can be re-used among multiple scopes.
"High priority" is only for blocking! Should only be used on "clicks"
- If there are no pending discrete events, then check for continuous events. These can be completely batched
- we batch completely until we run into a discrete event
- all continuous events are batched together
- so D C C C C C would be two separate events - D and C. IE onclick and onscroll
- D C C C C C C D C C C D would be D C D C D in 5 distinct phases.
- !listener bubbling is not currently implemented properly and will need to be implemented somehow in the future
- we need to keep track of element parents to be able to traverse properly
Open questions:
- what if we get two clicks from the component during the same slice?
- should we batch?
- react says no - they are continuous
- but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff?
+2 -2
View File
@@ -24,7 +24,7 @@ criterion_group!(mbenches, create_rows);
criterion_main!(mbenches);
fn create_rows(c: &mut Criterion) {
static App: FC<()> = |(cx, _)| {
static App: FC<()> = |cx, _| {
let mut rng = SmallRng::from_entropy();
let rows = (0..10_000_usize).map(|f| {
let label = Label::new(&mut rng);
@@ -58,7 +58,7 @@ struct RowProps {
row_id: usize,
label: Label,
}
fn Row((cx, props): Scope<RowProps>) -> Element {
fn Row(cx: Context, props: &RowProps) -> Element {
let [adj, col, noun] = props.label.0;
cx.render(rsx! {
tr {
+9
View File
@@ -0,0 +1,9 @@
use dioxus_core as dioxus;
use dioxus_core_macro::*;
fn main() {}
#[derive(Props)]
struct ChildProps<'a> {
name: &'a str,
}
+44
View File
@@ -0,0 +1,44 @@
use dioxus::prelude::*;
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
fn main() {
let _ = VirtualDom::new(Parent);
}
fn Parent(cx: Context, props: &()) -> Element {
let value = cx.use_hook(|_| String::new(), |f| &*f);
cx.render(rsx! {
div {
Child { name: value }
Fragment { "asd" }
}
})
}
#[derive(Props)]
struct ChildProps<'a> {
name: &'a str,
}
fn Child(cx: Context, props: &ChildProps) -> Element {
cx.render(rsx! {
div {
h1 { "it's nested" }
Child2 { name: props.name }
}
})
}
#[derive(Props)]
struct Grandchild<'a> {
name: &'a str,
}
fn Child2(cx: Context, props: &Grandchild) -> Element {
cx.render(rsx! {
div { "Hello {props.name}!" }
})
}
+6 -41
View File
@@ -5,43 +5,7 @@
//! if the type supports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
//! that ensures compile-time required and optional fields on cx.
use crate::{
innerlude::{Context, Element, VAnchor, VFragment, VNode},
LazyNodes, ScopeChildren,
};
/// A component is a wrapper around a Context and some Props that share a lifetime
///
///
/// # Example
///
/// With memoized state:
/// ```rust
/// struct State {}
///
/// fn Example((cx, props): Scope<State>) -> DomTree {
/// // ...
/// }
/// ```
///
/// With borrowed state:
/// ```rust
/// struct State<'a> {
/// name: &'a str
/// }
///
/// fn Example<'a>((cx, props): Scope<'a, State>) -> DomTree<'a> {
/// // ...
/// }
/// ```
///
/// With owned state as a closure:
/// ```rust
/// static Example: FC<()> = |(cx, props)| {
/// // ...
/// };
/// ```
///
pub type Scope<'a, T> = (Context<'a>, &'a T);
use crate::innerlude::{Context, Element, LazyNodes, ScopeChildren};
pub struct FragmentProps<'a> {
children: ScopeChildren<'a>,
@@ -51,7 +15,7 @@ pub struct FragmentBuilder<'a, const BUILT: bool> {
children: Option<ScopeChildren<'a>>,
}
impl<'a> FragmentBuilder<'a, false> {
pub fn children(mut self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> {
pub fn children(self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> {
FragmentBuilder {
children: Some(children),
}
@@ -75,7 +39,7 @@ impl<'a> Properties for FragmentProps<'a> {
FragmentBuilder { children: None }
}
unsafe fn memoize(&self, other: &Self) -> bool {
unsafe fn memoize(&self, _other: &Self) -> bool {
false
}
}
@@ -102,7 +66,7 @@ impl<'a> Properties for FragmentProps<'a> {
/// 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, props): Scope<'a, FragmentProps<'a>>) -> Element<'a> {
pub fn Fragment<'a>(cx: Context<'a>, props: &'a FragmentProps<'a>) -> Element {
cx.render(Some(LazyNodes::new(|f| {
f.fragment_from_iter(&props.children)
})))
@@ -173,6 +137,7 @@ impl EmptyBuilder {
/// 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<'a>) -> T::Builder {
pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Context<'a>, &'a T) -> Element) -> T::Builder {
// pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
T::builder()
}
-17
View File
@@ -1,17 +0,0 @@
//! Coroutines are just a "futures unordered" buffer for tasks that can be submitted through the use_coroutine hook.
//!
//! The idea here is to move *coroutine* support as a layer on top of *tasks*
use futures_util::{stream::FuturesUnordered, Future};
pub struct CoroutineScheduler {
futures: FuturesUnordered<Box<dyn Future<Output = ()>>>,
}
impl CoroutineScheduler {
pub fn new() -> Self {
CoroutineScheduler {
futures: FuturesUnordered::new(),
}
}
}
+393 -267
View File
File diff suppressed because it is too large. Load diff
-4
View File
@@ -49,10 +49,6 @@ impl<'bump> DiffStack<'bump> {
}
}
pub fn is_empty(&self) -> bool {
self.instructions.is_empty()
}
pub fn pop(&mut self) -> Option<DiffInstruction<'bump>> {
self.instructions.pop()
}
-50
View File
@@ -1,50 +0,0 @@
use std::collections::HashMap;
use fxhash::FxHashMap;
use crate::FC;
/// Provides heuristics to the "SharedResources" object for improving allocation performance.
///
/// This heuristics engine records the memory footprint of bump arenas and hook lists for each component. These records are
/// then used later on to optimize the initial allocation for future components. This helps save large allocations later on
/// that would slow down the diffing and initialization process.
///
///
pub struct HeuristicsEngine {
heuristics: FxHashMap<FcSlot, Heuristic>,
}
pub type FcSlot = *const ();
pub struct Heuristic {
hooks: u32,
bump_size: u64,
}
impl HeuristicsEngine {
pub(crate) fn new() -> Self {
Self {
heuristics: FxHashMap::default(),
}
}
fn recommend<T>(&mut self, fc: FC<T>, heuristic: Heuristic) {
let g = fc as FcSlot;
let e = self.heuristics.entry(g);
}
fn get_recommendation<T>(&mut self, fc: FC<T>) -> &Heuristic {
let id = fc as FcSlot;
self.heuristics.entry(id).or_insert(Heuristic {
bump_size: 100,
hooks: 10,
})
}
}
#[test]
fn types_work() {
let engine = HeuristicsEngine::new();
}
+20 -15
View File
@@ -1,10 +1,9 @@
use std::{
any::Any,
cell::{Cell, RefCell, UnsafeCell},
cell::{Cell, RefCell},
};
type UnsafeInnerHookState = UnsafeCell<Box<dyn Any>>;
type HookCleanup = Box<dyn FnOnce(Box<dyn Any>)>;
use bumpalo::Bump;
/// An abstraction over internally stored data using a hook-based memory layout.
///
@@ -15,7 +14,8 @@ type HookCleanup = Box<dyn FnOnce(Box<dyn Any>)>;
/// Todo: this could use its very own bump arena, but that might be a tad overkill
#[derive(Default)]
pub(crate) struct HookList {
vals: RefCell<Vec<(UnsafeInnerHookState, HookCleanup)>>,
arena: Bump,
vals: RefCell<Vec<*mut dyn Any>>,
idx: Cell<usize>,
}
@@ -23,7 +23,7 @@ impl HookList {
pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
self.vals.borrow().get(self.idx.get()).and_then(|inn| {
self.idx.set(self.idx.get() + 1);
let raw_box = unsafe { &mut *inn.0.get() };
let raw_box = unsafe { &mut **inn };
raw_box.downcast_mut::<T>()
})
}
@@ -34,14 +34,13 @@ impl HookList {
///
/// This should only be ran by Dioxus itself before "running scope".
/// Dioxus knows how to descend through the tree to prevent mutable aliasing.
pub(crate) unsafe fn reset(&mut self) {
pub(crate) unsafe fn reset(&self) {
self.idx.set(0);
}
pub(crate) fn push_hook<T: 'static>(&self, new: T, cleanup: HookCleanup) {
self.vals
.borrow_mut()
.push((UnsafeCell::new(Box::new(new)), cleanup))
pub(crate) fn push_hook<T: 'static>(&self, new: T) {
let val = self.arena.alloc(new);
self.vals.borrow_mut().push(val)
}
pub(crate) fn len(&self) -> usize {
@@ -57,10 +56,16 @@ impl HookList {
}
pub fn clear_hooks(&mut self) {
log::debug!("clearing hooks...");
self.vals
.borrow_mut()
.drain(..)
.for_each(|(state, cleanup)| cleanup(state.into_inner()));
self.vals.borrow_mut().drain(..).for_each(|state| {
let as_mut = unsafe { &mut *state };
let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
drop(boxed);
});
}
/// Get the ammount of memory a hooklist uses
/// Used in heuristics
pub fn get_hook_arena_size(&self) -> usize {
self.arena.allocated_bytes()
}
}
+166 -74
View File
@@ -27,89 +27,118 @@ pub struct LazyNodes<'a, 'b> {
inner: StackNodeStorage<'a, 'b>,
}
type StackHeapSize = [usize; 0];
type StackHeapSize = [usize; 8];
enum StackNodeStorage<'a, 'b> {
Stack(LazyStack),
Heap(Box<dyn FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b>),
Heap(Box<dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> + 'b>),
}
impl<'a, 'b> LazyNodes<'a, 'b> {
pub fn new<F>(val: F) -> Self
pub fn new<F>(_val: F) -> Self
where
F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b,
{
unsafe {
let mut ptr: *const _ = &val as &dyn FnOnce(NodeFactory<'a>) -> VNode<'a>;
// 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);
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...)"
let val = move |fac: Option<NodeFactory<'a>>| {
let inner = slot.take().unwrap();
fac.map(inner)
};
unsafe { LazyNodes::new_inner(val) }
}
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 {
log::debug!(
"lazy nodes was too large to fit into stack. falling back to heap. max: {}, actual {:?}",
max_size,
stored_size
);
Self {
inner: StackNodeStorage::Heap(Box::new(val)),
}
} else {
log::debug!(
"lazy nodes fits on stack! max: {}, actual: {:?}",
max_size,
stored_size
);
let mut buf: StackHeapSize = StackHeapSize::default();
// - 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>()
);
assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
let info = &words[1..];
let data = words[0] as *mut ();
let size = mem::size_of::<F>();
if info.len() * mem::size_of::<usize>() + size > mem::size_of::<StackHeapSize>() {
log::debug!("lazy nodes was too large to fit into stack. falling back to heap");
Self {
inner: StackNodeStorage::Heap(Box::new(val)),
// 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;
}
} else {
log::debug!("lazy nodes fits on stack!");
let mut buf: StackHeapSize = StackHeapSize::default();
}
assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
let src_ptr = data as *const u8;
let dataptr = buf.as_mut()[..].as_mut_ptr() as *mut u8;
for i in 0..size {
*dataptr.add(i) = *src_ptr.add(i);
}
// 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;
}
}
std::mem::forget(val);
let src_ptr = data as *const u8;
let dataptr = buf.as_mut()[..].as_mut_ptr() as *mut u8;
for i in 0..size {
*dataptr.add(i) = *src_ptr.add(i);
}
std::mem::forget(val);
Self {
inner: StackNodeStorage::Stack(LazyStack { _align: [], buf }),
}
Self {
inner: StackNodeStorage::Stack(LazyStack {
_align: [],
buf,
dropped: false,
}),
}
}
}
pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> {
match self.inner {
StackNodeStorage::Heap(lazy) => lazy(f),
StackNodeStorage::Stack(stack) => stack.call(f),
StackNodeStorage::Heap(mut lazy) => lazy(Some(f)).unwrap(),
StackNodeStorage::Stack(mut stack) => stack.call(f),
}
}
}
@@ -117,34 +146,51 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
struct LazyStack {
_align: [u64; 0],
buf: StackHeapSize,
dropped: bool,
}
impl LazyStack {
unsafe fn create_boxed<'a>(&mut self) -> Box<dyn FnOnce(NodeFactory<'a>) -> VNode<'a>> {
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 FnOnce(NodeFactory<'a>) -> VNode<'a>>()
/ mem::size_of::<usize>()
- 1;
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 FnOnce(NodeFactory<'a>) -> VNode<'a> =
make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]);
let g: *mut dyn FnMut(Option<NodeFactory<'a>>) -> Option<VNode<'a>> =
unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
Box::from_raw(g)
}
self.dropped = true;
fn call(mut self, f: NodeFactory) -> VNode {
let boxed = unsafe { self.create_boxed() };
boxed(f)
let clos = unsafe { &mut *g };
clos(Some(f)).unwrap()
}
}
impl Drop for LazyStack {
fn drop(&mut self) {
let boxed = unsafe { self.create_boxed() };
mem::drop(boxed);
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);
}
}
}
@@ -177,7 +223,7 @@ fn round_to_words(len: usize) -> usize {
fn it_works() {
let bump = bumpalo::Bump::new();
simple_logger::init();
simple_logger::init().unwrap();
let factory = NodeFactory { bump: &bump };
@@ -191,3 +237,49 @@ fn it_works() {
dbg!(g);
}
#[test]
fn it_drops() {
use std::rc::Rc;
let bump = bumpalo::Bump::new();
simple_logger::init().unwrap();
let factory = NodeFactory { bump: &bump };
struct DropInner {
id: i32,
}
impl Drop for DropInner {
fn drop(&mut self) {
log::debug!("dropping inner");
}
}
let val = Rc::new(10);
{
let it = (0..10)
.map(|i| {
let val = val.clone();
NodeFactory::annotate_lazy(move |f| {
log::debug!("hell closure");
let inner = DropInner { id: i };
f.text(format_args!("hello world {:?}, {:?}", inner.id, val))
})
})
.collect::<Vec<_>>();
let caller = NodeFactory::annotate_lazy(|f| {
log::debug!("main closure");
f.fragment_from_iter(it)
})
.unwrap();
}
// let nodes = caller.call(factory);
// dbg!(nodes);
dbg!(Rc::strong_count(&val));
}
+11 -30
View File
@@ -12,68 +12,49 @@ Navigating this crate:
Some utilities
*/
pub(crate) mod bumpframe;
pub(crate) mod childiter;
pub(crate) mod component;
pub(crate) mod coroutines;
pub(crate) mod diff;
pub(crate) mod diff_stack;
pub(crate) mod events;
pub(crate) mod heuristics;
pub(crate) mod hooklist;
pub(crate) mod hooks;
pub(crate) mod lazynodes;
pub(crate) mod mutations;
pub(crate) mod nodes;
pub(crate) mod resources;
pub(crate) mod scheduler;
pub(crate) mod scope;
pub(crate) mod tasks;
pub(crate) mod scopearena;
pub(crate) mod test_dom;
pub(crate) mod threadsafe;
pub(crate) mod util;
pub(crate) mod virtual_dom;
#[cfg(feature = "debug_vdom")]
pub mod debug_dom;
pub(crate) mod innerlude {
pub(crate) use crate::bumpframe::*;
pub(crate) use crate::childiter::*;
pub use crate::component::*;
pub(crate) use crate::diff::*;
pub use crate::diff_stack::*;
pub use crate::events::*;
pub use crate::heuristics::*;
pub(crate) use crate::hooklist::*;
pub use crate::hooks::*;
pub use crate::lazynodes::*;
pub use crate::mutations::*;
pub use crate::nodes::*;
pub(crate) use crate::resources::*;
pub use crate::scheduler::*;
pub use crate::scope::*;
pub use crate::tasks::*;
pub use crate::scopearena::*;
pub use crate::test_dom::*;
pub use crate::threadsafe::*;
pub use crate::util::*;
pub use crate::virtual_dom::*;
pub type Element<'a> = Option<VNode<'a>>;
pub type FC<P> = for<'a> fn(Scope<'a, P>) -> Element<'a>;
pub type Element = Option<NodeLink>;
pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> Element;
}
pub use crate::innerlude::{
Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, MountType,
Mutations, NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId, TaskHandle, TestDom,
ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
Attribute, Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes,
Listener, MountType, Mutations, NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId,
TestDom, UserEvent, VAnchor, VElement, VFragment, VNode, VSuspended, VirtualDom, FC,
};
pub mod prelude {
pub use crate::component::{fc_to_builder, Fragment, Properties, Scope};
pub use crate::hooks::*;
pub use crate::component::{fc_to_builder, Fragment, Properties};
pub use crate::innerlude::Context;
pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, ScopeChildren, FC};
pub use crate::innerlude::{
DioxusElement, Element, LazyNodes, NodeFactory, Scope, ScopeChildren, FC,
};
pub use crate::nodes::VNode;
pub use crate::VirtualDom;
}
+115 -43
View File
@@ -4,7 +4,7 @@
//! cheap and *very* fast to construct - building a full tree should be quick.
use crate::{
innerlude::{empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, ScopeInner},
innerlude::{empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId},
lazynodes::LazyNodes,
};
use bumpalo::{boxed::Box as BumpBox, Bump};
@@ -14,6 +14,19 @@ use std::{
fmt::{Arguments, Debug, Formatter},
};
/// A cached node is a "pointer" to a "rendered" node in a particular scope
///
/// It does not provide direct access to the node, so it doesn't carry any lifetime information with it
///
/// It is used during the diffing/rendering process as a runtime key into an existing set of nodes. The "render" key
/// is essentially a unique key to guarantee safe usage of the Node.
#[derive(Clone, Debug)]
pub struct NodeLink {
pub(crate) link_idx: usize,
pub(crate) gen_id: u32,
pub(crate) scope_id: ScopeId,
}
/// 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:
@@ -118,6 +131,13 @@ pub enum VNode<'src> {
/// }
/// ```
Anchor(&'src VAnchor),
/// A type of node that links this node to another scope or render cycle
///
/// Is essentially a "pointer" to a "rendered" node in a particular scope
///
/// Used in portals
Linked(NodeLink),
}
impl<'src> VNode<'src> {
@@ -127,9 +147,11 @@ impl<'src> VNode<'src> {
VNode::Element(el) => el.key,
VNode::Component(c) => c.key,
VNode::Fragment(f) => f.key,
VNode::Text(_t) => None,
VNode::Suspended(_s) => None,
VNode::Anchor(_f) => None,
VNode::Linked(_c) => None,
}
}
@@ -149,11 +171,20 @@ impl<'src> VNode<'src> {
VNode::Element(el) => el.dom_id.get(),
VNode::Anchor(el) => el.dom_id.get(),
VNode::Suspended(el) => el.dom_id.get(),
VNode::Linked(_) => None,
VNode::Fragment(_) => None,
VNode::Component(_) => None,
}
}
pub fn children(&self) -> &[VNode<'src>] {
match &self {
VNode::Fragment(f) => f.children,
VNode::Component(c) => todo!("children are not accessible through this"),
_ => &[],
}
}
// Create an "owned" version of the vnode.
pub fn decouple(&self) -> VNode<'src> {
match self {
@@ -165,7 +196,11 @@ impl<'src> VNode<'src> {
VNode::Fragment(f) => VNode::Fragment(VFragment {
children: f.children,
key: f.key,
is_static: f.is_static,
}),
VNode::Linked(c) => VNode::Linked(NodeLink {
gen_id: c.gen_id,
scope_id: c.scope_id,
link_idx: c.link_idx,
}),
}
}
@@ -175,17 +210,24 @@ impl Debug for VNode<'_> {
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match &self {
VNode::Element(el) => s
.debug_struct("VElement")
.debug_struct("VNode::VElement")
.field("name", &el.tag_name)
.field("key", &el.key)
.finish(),
VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text),
VNode::Anchor(_) => write!(s, "VAnchor"),
VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
VNode::Anchor(_) => write!(s, "VNode::VAnchor"),
VNode::Fragment(frag) => write!(s, "VFragment {{ children: {:?} }}", frag.children),
VNode::Suspended { .. } => write!(s, "VSuspended"),
VNode::Component(comp) => write!(s, "VComponent {{ fc: {:?}}}", comp.user_fc),
VNode::Fragment(frag) => {
write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
}
VNode::Suspended { .. } => write!(s, "VNode::VSuspended"),
VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
VNode::Linked(c) => write!(
s,
"VNode::VCached {{ gen_id: {}, scope_id: {:?} }}",
c.gen_id, c.scope_id
),
}
}
}
@@ -209,8 +251,6 @@ pub struct VFragment<'src> {
pub key: Option<&'src str>,
pub children: &'src [VNode<'src>],
pub is_static: bool,
}
/// An element like a "div" with children, listeners, and attributes.
@@ -309,6 +349,7 @@ pub struct Listener<'bump> {
pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(Box<dyn Any + Send>) + 'bump>>>,
}
pub type VCompCaller<'src> = BumpBox<'src, dyn Fn(Context) -> Element + 'src>;
/// Virtual Components for custom user-defined components
/// Only supports the functional syntax
pub struct VComponent<'src> {
@@ -316,21 +357,19 @@ pub struct VComponent<'src> {
pub associated_scope: Cell<Option<ScopeId>>,
pub is_static: bool,
// Function pointer to the FC that was used to generate this component
pub user_fc: *const (),
pub(crate) caller: BumpBox<'src, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> + 'src>,
pub(crate) comparator: Option<BumpBox<'src, dyn Fn(&VComponent) -> bool + 'src>>,
pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
pub(crate) can_memoize: bool,
pub(crate) hard_allocation: Cell<Option<*const ()>>,
// Raw pointer into the bump arena for the props of the component
pub(crate) raw_props: *const (),
pub(crate) bump_props: *const (),
// during the "teardown" process we'll take the caller out so it can be dropped properly
pub(crate) caller: Option<VCompCaller<'src>>,
pub(crate) comparator: Option<BumpBox<'src, dyn Fn(&VComponent) -> bool + 'src>>,
}
pub struct VSuspended<'a> {
@@ -338,7 +377,7 @@ pub struct VSuspended<'a> {
pub dom_id: Cell<Option<ElementId>>,
#[allow(clippy::type_complexity)]
pub callback: RefCell<Option<BumpBox<'a, dyn FnMut() -> Element<'a> + 'a>>>,
pub callback: RefCell<Option<BumpBox<'a, dyn FnMut() -> Element + 'a>>>,
}
/// This struct provides an ergonomic API to quickly build VNodes.
@@ -475,16 +514,30 @@ impl<'a> NodeFactory<'a> {
pub fn component<P>(
&self,
component: fn(Scope<'a, P>) -> Element<'a>,
component: fn(Context<'a>, &'a P) -> Element,
props: P,
key: Option<Arguments>,
) -> VNode<'a>
where
P: Properties + 'a,
{
/*
our strategy:
- unsafe hackery
- lol
- we don't want to hit the global allocator
- allocate into our bump arena
- if the props aren't static, then we convert them into a box which we pass off between renders
*/
// let bump = self.bump();
// // later, we'll do a hard allocation
// let raw_ptr = bump.alloc(props);
let bump = self.bump();
let props = bump.alloc(props);
let raw_props = props as *mut P as *mut ();
let bump_props = props as *mut P as *mut ();
let user_fc = component as *const ();
let comparator: &mut dyn Fn(&VComponent) -> bool = bump.alloc_with(|| {
@@ -496,7 +549,7 @@ impl<'a> NodeFactory<'a> {
// - Non-static P are autoderived to memoize as false
// - This comparator is only called on a corresponding set of bumpframes
let props_memoized = unsafe {
let real_other: &P = &*(other.raw_props as *const _ as *const P);
let real_other: &P = &*(other.bump_props as *const _ as *const P);
props.memoize(real_other)
};
@@ -508,7 +561,6 @@ impl<'a> NodeFactory<'a> {
}
}
});
let comparator = Some(unsafe { BumpBox::from_raw(comparator) });
let drop_props = {
// create a closure to drop the props
@@ -518,7 +570,7 @@ impl<'a> NodeFactory<'a> {
move || unsafe {
log::debug!("dropping props!");
if !has_dropped {
let real_other = raw_props as *mut _ as *mut P;
let real_other = bump_props as *mut _ as *mut P;
let b = BumpBox::from_raw(real_other);
std::mem::drop(b);
@@ -536,33 +588,41 @@ impl<'a> NodeFactory<'a> {
let key = key.map(|f| self.raw_text(f).0);
let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> =
bump.alloc(move |scope: &ScopeInner| -> Element {
let caller: &'a mut dyn Fn(&Scope) -> Element =
bump.alloc(move |scope: &Scope| -> Element {
log::debug!("calling component renderr {:?}", scope.our_arena_idx);
let props: &'_ P = unsafe { &*(raw_props as *const P) };
let scp: &'a ScopeInner = unsafe { std::mem::transmute(scope) };
let s: Scope<'a, P> = (scp, props);
let res: Element = component(s);
let props: &'_ P = unsafe { &*(bump_props as *const P) };
let res = component(scope, props);
// let res = component((Context { scope }, props));
unsafe { std::mem::transmute(res) }
});
let caller = unsafe { BumpBox::from_raw(caller) };
let can_memoize = P::IS_STATIC;
VNode::Component(bump.alloc(VComponent {
user_fc,
comparator,
raw_props,
bump_props,
caller,
is_static: P::IS_STATIC,
key,
can_memoize: P::IS_STATIC,
can_memoize,
drop_props,
associated_scope: Cell::new(None),
}))
}
pub fn listener(
self,
event: &'static str,
callback: BumpBox<'a, dyn FnMut(Box<dyn Any + Send>) + 'a>,
) -> Listener<'a> {
Listener {
mounted_node: Cell::new(None),
event,
callback: RefCell::new(Some(callback)),
}
}
pub fn fragment_from_iter(
self,
node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
@@ -604,14 +664,12 @@ impl<'a> NodeFactory<'a> {
VNode::Fragment(VFragment {
children,
key: None,
is_static: false,
})
}
pub fn annotate_lazy<'z, 'b, F>(f: F) -> Option<LazyNodes<'z, 'b>>
where
F: FnOnce(NodeFactory<'z>) -> VNode<'z> + 'b,
{
pub fn annotate_lazy<'z, 'b>(
f: impl FnOnce(NodeFactory<'z>) -> VNode<'z> + 'b,
) -> Option<LazyNodes<'z, 'b>> {
Some(LazyNodes::new(f))
}
}
@@ -672,6 +730,21 @@ impl IntoVNode<'_> for Option<()> {
}
}
// Conveniently, we also support "None"
impl IntoVNode<'_> for Option<NodeLink> {
fn into_vnode(self, cx: NodeFactory) -> VNode {
todo!()
// cx.fragment_from_iter(None as Option<VNode>)
}
}
// Conveniently, we also support "None"
impl IntoVNode<'_> for NodeLink {
fn into_vnode(self, cx: NodeFactory) -> VNode {
todo!()
// cx.fragment_from_iter(None as Option<VNode>)
}
}
impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
self.unwrap_or_else(|| cx.fragment_from_iter(None as Option<VNode>))
@@ -684,7 +757,6 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
Some(lazy) => lazy.call(cx),
None => VNode::Fragment(VFragment {
children: &[],
is_static: false,
key: None,
}),
}
@@ -3,69 +3,51 @@ use bumpalo::Bump;
use std::cell::Cell;
pub(crate) struct ActiveFrame {
// We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
pub generation: Cell<usize>,
pub cur_generation: Cell<usize>,
// The double-buffering situation that we will use
pub frames: [BumpFrame; 2],
}
pub(crate) struct BumpFrame {
pub bump: Bump,
pub(crate) head_node: VNode<'static>,
#[cfg(test)]
// used internally for debugging
_name: &'static str,
}
impl ActiveFrame {
pub fn new() -> Self {
let b1 = Bump::new();
let b2 = Bump::new();
let frame_a = BumpFrame {
head_node: VNode::Fragment(VFragment {
key: None,
children: &[],
is_static: false,
}),
bump: b1,
#[cfg(test)]
_name: "wip",
generation: 0.into(),
};
let frame_b = BumpFrame {
head_node: VNode::Fragment(VFragment {
key: None,
children: &[],
is_static: false,
}),
bump: b2,
#[cfg(test)]
_name: "fin",
};
Self {
generation: 0.into(),
};
Self {
frames: [frame_a, frame_b],
cur_generation: 0.into(),
}
}
pub unsafe fn reset_wip_frame(&mut self) {
self.wip_frame_mut().bump.reset()
pub unsafe fn reset_wip_frame(&self) {
// todo: unsafecell or something
let bump = self.wip_frame() as *const _ as *mut BumpFrame;
let g = &mut *bump;
g.bump.reset();
// self.wip_frame_mut().bump.reset()
}
/// The "work in progress frame" represents the frame that is currently being worked on.
pub fn wip_frame(&self) -> &BumpFrame {
match self.generation.get() & 1 == 0 {
match self.cur_generation.get() & 1 == 0 {
true => &self.frames[0],
false => &self.frames[1],
}
}
pub fn wip_frame_mut(&mut self) -> &mut BumpFrame {
match self.generation.get() & 1 == 0 {
match self.cur_generation.get() & 1 == 0 {
true => &mut self.frames[0],
false => &mut self.frames[1],
}
@@ -73,26 +55,26 @@ impl ActiveFrame {
/// The finished frame represents the frame that has been "finished" and cannot be modified again
pub fn finished_frame(&self) -> &BumpFrame {
match self.generation.get() & 1 == 1 {
match self.cur_generation.get() & 1 == 1 {
true => &self.frames[0],
false => &self.frames[1],
}
}
/// Give out our self-referential item with our own borrowed lifetime
pub fn fin_head<'b>(&'b self) -> &'b VNode<'b> {
let cur_head = &self.finished_frame().head_node;
unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
}
// /// Give out our self-referential item with our own borrowed lifetime
// pub fn fin_head<'b>(&'b self) -> &'b VNode<'b> {
// let cur_head = &self.finished_frame().head_node;
// unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
// }
/// Give out our self-referential item with our own borrowed lifetime
pub fn wip_head<'b>(&'b self) -> &'b VNode<'b> {
let cur_head = &self.wip_frame().head_node;
unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
}
// /// Give out our self-referential item with our own borrowed lifetime
// pub fn wip_head<'b>(&'b self) -> &'b VNode<'b> {
// let cur_head = &self.wip_frame().head_node;
// unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
// }
pub fn cycle_frame(&mut self) {
self.generation.set(self.generation.get() + 1);
self.cur_generation.set(self.cur_generation.get() + 1);
}
}
@@ -119,6 +101,6 @@ mod tests {
assert_eq!(fin._name, "wip");
frames.cycle_frame();
}
assert_eq!(frames.generation.get(), 10);
assert_eq!(frames.cur_generation.get(), 10);
}
}
@@ -62,7 +62,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
VNode::Component(sc) => {
let scope = self
.scopes
.get_scope(sc.associated_scope.get().unwrap())
.get_scope(&sc.associated_scope.get().unwrap())
.unwrap();
// Simply swap the current node on the stack with the root of the component
@@ -1,4 +1,4 @@
use crate::{innerlude::ScopeInner, virtual_dom::VirtualDom, VNode};
use crate::{innerlude::ScopeState, virtual_dom::VirtualDom, VNode};
impl std::fmt::Display for VirtualDom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@@ -24,7 +24,7 @@ impl std::fmt::Display for VirtualDom {
pub(crate) struct ScopeRenderer<'a> {
pub skip_components: bool,
pub show_fragments: bool,
pub _scope: &'a ScopeInner,
pub _scope: &'a ScopeState,
pub _pre_render: bool,
pub _newline: bool,
pub _indent: bool,
@@ -48,6 +48,9 @@ impl<'a> ScopeRenderer<'a> {
};
match &node {
VNode::Linked(_) => {
write!(f, "Linked").unwrap();
}
VNode::Text(text) => {
write_indent(f, il);
writeln!(f, "\"{}\"", text.text)?
@@ -115,7 +118,7 @@ impl<'a> ScopeRenderer<'a> {
VNode::Component(vcomp) => {
let idx = vcomp.associated_scope.get().unwrap();
if !self.skip_components {
let new_node = vdom.get_scope(idx).unwrap().root_node();
let new_node = vdom.get_scope(&idx).unwrap().root_node();
self.render(vdom, new_node, f, il)?;
}
}
+15
View File
@@ -0,0 +1,15 @@
//! An event system that's less confusing than Traits + RC;
//! This should hopefully make it easier to port to other platforms.
//!
//! Unfortunately, it is less efficient than the original, but hopefully it's negligible.
use crate::{
innerlude::Listener,
innerlude::{ElementId, NodeFactory, ScopeId},
};
use bumpalo::boxed::Box as BumpBox;
use std::{
any::Any,
cell::{Cell, RefCell},
fmt::Debug,
};
@@ -13,73 +13,73 @@ use crate::innerlude::*;
use futures_util::FutureExt;
use std::{any::Any, cell::RefCell, future::Future, ops::Deref, rc::Rc};
/// Awaits the given task, forcing the component to re-render when the value is ready.
///
/// Returns the handle to the task and the value (if it is ready, else None).
///
/// ```
/// static Example: FC<()> = |(cx, props)| {
/// let (task, value) = use_task(|| async {
/// timer::sleep(Duration::from_secs(1)).await;
/// "Hello World"
/// });
///
/// match contents {
/// Some(contents) => rsx!(cx, div { "{title}" }),
/// None => rsx!(cx, div { "Loading..." }),
/// }
/// };
/// ```
pub fn use_coroutine<'src, Out, Fut, Init>(
cx: Context<'src>,
task_initializer: Init,
) -> (&'src TaskHandle, &'src Option<Out>)
where
Out: 'static,
Fut: Future<Output = Out> + 'static,
Init: FnOnce() -> Fut + 'src,
{
struct TaskHook<T> {
handle: TaskHandle,
task_dump: Rc<RefCell<Option<T>>>,
value: Option<T>,
}
// /// Awaits the given task, forcing the component to re-render when the value is ready.
// ///
// /// Returns the handle to the task and the value (if it is ready, else None).
// ///
// /// ```
// /// static Example: FC<()> = |(cx, props)| {
// /// let (task, value) = use_task(|| async {
// /// timer::sleep(Duration::from_secs(1)).await;
// /// "Hello World"
// /// });
// ///
// /// match contents {
// /// Some(contents) => rsx!(cx, div { "{title}" }),
// /// None => rsx!(cx, div { "Loading..." }),
// /// }
// /// };
// /// ```
// pub fn use_coroutine<'src, Out, Fut, Init>(
// cx: Context<'src>,
// task_initializer: Init,
// ) -> (&'src TaskHandle, &'src Option<Out>)
// where
// Out: 'static,
// Fut: Future<Output = Out> + 'static,
// Init: FnOnce() -> Fut + 'src,
// {
// struct TaskHook<T> {
// handle: TaskHandle,
// task_dump: Rc<RefCell<Option<T>>>,
// value: Option<T>,
// }
todo!()
// todo!()
// // whenever the task is complete, save it into th
// cx.use_hook(
// move |_| {
// let task_fut = task_initializer();
// // // whenever the task is complete, save it into th
// // cx.use_hook(
// // move |_| {
// // let task_fut = task_initializer();
// let task_dump = Rc::new(RefCell::new(None));
// // let task_dump = Rc::new(RefCell::new(None));
// let slot = task_dump.clone();
// // let slot = task_dump.clone();
// let updater = cx.schedule_update_any();
// let originator = cx.scope.our_arena_idx;
// // let updater = cx.schedule_update_any();
// // let originator = cx.scope.our_arena_idx;
// let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
// *slot.as_ref().borrow_mut() = Some(output);
// updater(originator);
// originator
// })));
// // let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
// // *slot.as_ref().borrow_mut() = Some(output);
// // updater(originator);
// // originator
// // })));
// TaskHook {
// task_dump,
// value: None,
// handle,
// }
// },
// |hook| {
// if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
// hook.value = Some(val);
// }
// (&hook.handle, &hook.value)
// },
// |_| {},
// )
}
// // TaskHook {
// // task_dump,
// // value: None,
// // handle,
// // }
// // },
// // |hook| {
// // if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
// // hook.value = Some(val);
// // }
// // (&hook.handle, &hook.value)
// // },
// // |_| {},
// // )
// }
/// Asynchronously render new nodes once the given future has completed.
///
@@ -95,11 +95,11 @@ pub fn use_suspense<'src, Out, Fut, Cb>(
cx: Context<'src>,
task_initializer: impl FnOnce() -> Fut,
user_callback: Cb,
) -> Element<'src>
) -> Element
where
Fut: Future<Output = Out>,
Out: 'static,
Cb: FnMut(&Out) -> Element<'src> + 'src,
Cb: FnMut(&Out) -> Element + 'src,
{
/*
General strategy:
@@ -167,10 +167,10 @@ where
// )
}
pub(crate) struct SuspenseHook {
pub handle: TaskHandle,
pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
}
// pub(crate) struct SuspenseHook {
// pub handle: TaskHandle,
// pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
// }
#[derive(Clone, Copy)]
pub struct NodeRef<'src, T: 'static>(&'src RefCell<Option<T>>);
@@ -183,5 +183,5 @@ impl<'a, T> Deref for NodeRef<'a, T> {
}
pub fn use_node_ref<T, P>(cx: Context) -> NodeRef<T> {
cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f }, |_| {})
cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f })
}
File renamed without changes.
@@ -32,18 +32,18 @@ pub(crate) struct ResourcePool {
impl ResourcePool {
/// this is unsafe because the caller needs to track which other scopes it's already using
pub fn get_scope(&self, idx: ScopeId) -> Option<&ScopeInner> {
pub fn get_scope(&self, idx: &ScopeId) -> Option<&ScopeInner> {
let inner = unsafe { &*self.components.get() };
inner.get(idx.0)
}
/// this is unsafe because the caller needs to track which other scopes it's already using
pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut ScopeInner> {
pub fn get_scope_mut(&self, idx: &ScopeId) -> Option<&mut ScopeInner> {
let inner = unsafe { &mut *self.components.get() };
inner.get_mut(idx.0)
}
pub fn try_remove(&self, id: ScopeId) -> Option<ScopeInner> {
pub fn try_remove(&self, id: &ScopeId) -> Option<ScopeInner> {
let inner = unsafe { &mut *self.components.get() };
Some(inner.remove(id.0))
// .try_remove(id.0)
@@ -68,14 +68,17 @@ do anything too arduous from onInput.
For the rest, we defer to the rIC period and work down each queue from high to low.
*/
use crate::heuristics::*;
use crate::innerlude::*;
use bumpalo::Bump;
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt};
use fxhash::FxHashMap;
use fxhash::FxHashSet;
use indexmap::IndexSet;
use slab::Slab;
use std::pin::Pin;
use std::task::Poll;
use std::{
any::{Any, TypeId},
cell::{Cell, UnsafeCell},
@@ -85,11 +88,7 @@ use std::{
#[derive(Clone)]
pub(crate) struct EventChannel {
pub task_counter: Rc<Cell<u64>>,
pub cur_subtree: Rc<Cell<u32>>,
pub sender: UnboundedSender<SchedulerMsg>,
pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
pub submit_task: Rc<dyn Fn(FiberTask) -> TaskHandle>,
pub get_shared_context: GetSharedContext,
}
@@ -101,16 +100,6 @@ pub enum SchedulerMsg {
// setstate
Immediate(ScopeId),
// tasks
Task(TaskMsg),
}
pub enum TaskMsg {
ToggleTask(u64),
PauseTask(u64),
ResumeTask(u64),
DropTask(u64),
}
/// The scheduler holds basically everything around "working"
@@ -126,16 +115,26 @@ pub enum TaskMsg {
///
/// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
///
/// There's a lot of raw pointers here...
///
/// Since we're building self-referential structures for each component, we need to make sure that the referencs stay stable
/// The best way to do that is a bump allocator.
///
///
///
pub(crate) struct Scheduler {
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
/// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
///
/// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
/// and rusts's guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
pub pool: ResourcePool,
// /// All mounted components are arena allocated to make additions, removals, and references easy to work with
// /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
// ///
// /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
// /// and rusts's guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
// pub pool: ResourcePool,
//
pub component_arena: Bump,
pub heuristics: HeuristicsEngine,
pub free_components: VecDeque<*mut ScopeInner>,
pub heuristics: FxHashMap<FcSlot, Heuristic>,
pub receiver: UnboundedReceiver<SchedulerMsg>,
@@ -145,104 +144,70 @@ pub(crate) struct Scheduler {
// Every component that has futures that need to be polled
pub pending_futures: FxHashSet<ScopeId>,
// In-flight futures
pub async_tasks: FuturesUnordered<FiberTask>,
// // scheduler stuff
// pub current_priority: EventPriority,
pub ui_events: VecDeque<UserEvent>,
pub pending_immediates: VecDeque<ScopeId>,
pub pending_tasks: VecDeque<UserEvent>,
pub batched_events: VecDeque<UserEvent>,
pub garbage_scopes: HashSet<ScopeId>,
pub dirty_scopes: IndexSet<ScopeId>,
pub saved_state: Option<SavedDiffWork<'static>>,
pub in_progress: bool,
}
pub type FcSlot = *const ();
pub struct Heuristic {
hook_arena_size: usize,
node_arena_size: usize,
}
impl Scheduler {
pub(crate) fn new(
sender: UnboundedSender<SchedulerMsg>,
receiver: UnboundedReceiver<SchedulerMsg>,
component_capacity: usize,
element_capacity: usize,
) -> Self {
/*
Preallocate 2000 elements and 100 scopes to avoid dynamic allocation.
Perhaps this should be configurable from some external config?
*/
let components = Rc::new(UnsafeCell::new(Slab::with_capacity(100)));
let raw_elements = Rc::new(UnsafeCell::new(Slab::with_capacity(2000)));
let heuristics = HeuristicsEngine::new();
let task_counter = Rc::new(Cell::new(0));
let cur_subtree = Rc::new(Cell::new(0));
// let components = Rc::new(UnsafeCell::new(Slab::with_capacity(component_capacity)));
let raw_elements = Rc::new(UnsafeCell::new(Slab::with_capacity(element_capacity)));
let channel = EventChannel {
cur_subtree,
task_counter: task_counter.clone(),
sender: sender.clone(),
schedule_any_immediate: {
let sender = sender.clone();
Rc::new(move |id| {
//
log::debug!("scheduling immediate! {:?}", id);
sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap()
})
},
// todo: we want to get the futures out of the scheduler message
// the scheduler message should be send/sync
submit_task: {
Rc::new(move |fiber_task| {
let task_id = task_counter.get();
task_counter.set(task_id + 1);
todo!();
// sender
// .unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask(
// fiber_task, task_id,
// )))
// .unwrap();
TaskHandle {
our_id: task_id,
sender: sender.clone(),
}
})
},
get_shared_context: {
let components = components.clone();
Rc::new(move |id, ty| {
let components = unsafe { &*components.get() };
let mut search: Option<&ScopeInner> = components.get(id.0);
while let Some(inner) = search.take() {
if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
return Some(shared.clone());
} else {
search = inner.parent_idx.map(|id| components.get(id.0)).flatten();
}
}
None
})
todo!()
// let components = components.clone();
// Rc::new(move |id, ty| {
// let components = unsafe { &*components.get() };
// let mut search: Option<&ScopeInner> = components.get(id.0);
// while let Some(inner) = search.take() {
// if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
// return Some(shared.clone());
// } else {
// search = inner.parent_idx.map(|id| components.get(id.0)).flatten();
// }
// }
// None
// })
},
};
let pool = ResourcePool {
components,
raw_elements,
channel,
};
let async_tasks = FuturesUnordered::new();
// push a task that would never resolve - prevents us from immediately aborting the scheduler
async_tasks.push(Box::pin(async {
std::future::pending::<()>().await;
ScopeId(0)
}) as FiberTask);
// let pool = ResourcePool {
// components,
// raw_elements,
// channel,
// };
let saved_state = SavedDiffWork {
mutations: Mutations::new(),
@@ -251,22 +216,15 @@ impl Scheduler {
};
Self {
pool,
// pool,
receiver,
async_tasks,
pending_garbage: FxHashSet::default(),
heuristics,
ui_events: VecDeque::new(),
pending_immediates: VecDeque::new(),
pending_tasks: VecDeque::new(),
batched_events: VecDeque::new(),
garbage_scopes: HashSet::new(),
@@ -275,6 +233,8 @@ impl Scheduler {
dirty_scopes: Default::default(),
saved_state: Some(saved_state),
in_progress: false,
heuristics: todo!(),
}
}
@@ -282,7 +242,7 @@ impl Scheduler {
pub fn handle_ui_event(&mut self, event: UserEvent) -> bool {
let (discrete, priority) = event_meta(&event);
if let Some(scope) = self.pool.get_scope_mut(event.scope) {
if let Some(scope) = self.get_scope_mut(&event.scope) {
if let Some(element) = event.mounted_dom_id {
// TODO: bubble properly here
scope.call_listener(event, element);
@@ -308,7 +268,7 @@ impl Scheduler {
fn prepare_work(&mut self) {
// while let Some(trigger) = self.ui_events.pop_back() {
// if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {}
// if let Some(scope) = self.get_scope_mut(&trigger.scope) {}
// }
}
@@ -328,15 +288,9 @@ impl Scheduler {
unsafe fn load_work(&mut self) -> SavedDiffWork<'static> {
self.saved_state.take().unwrap().extend()
}
pub fn handle_task(&mut self, evt: TaskMsg) {
//
}
pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
match msg {
//
SchedulerMsg::Task(msg) => todo!(),
SchedulerMsg::Immediate(_) => todo!(),
SchedulerMsg::UiEvent(event) => {
@@ -344,7 +298,7 @@ impl Scheduler {
let (discrete, priority) = event_meta(&event);
if let Some(scope) = self.pool.get_scope_mut(event.scope) {
if let Some(scope) = self.get_scope_mut(&event.scope) {
if let Some(element) = event.mounted_dom_id {
// TODO: bubble properly here
scope.call_listener(event, element);
@@ -374,19 +328,19 @@ impl Scheduler {
let saved_state = unsafe { self.load_work() };
// We have to split away some parts of ourself - current lane is borrowed mutably
let shared = self.pool.clone();
let shared = self.clone();
let mut machine = unsafe { saved_state.promote(&shared) };
let mut ran_scopes = FxHashSet::default();
if machine.stack.is_empty() {
let shared = self.pool.clone();
let shared = self.clone();
self.dirty_scopes
.retain(|id| shared.get_scope(*id).is_some());
.retain(|id| shared.get_scope(id).is_some());
self.dirty_scopes.sort_by(|a, b| {
let h1 = shared.get_scope(*a).unwrap().height;
let h2 = shared.get_scope(*b).unwrap().height;
let h1 = shared.get_scope(a).unwrap().height;
let h2 = shared.get_scope(b).unwrap().height;
h1.cmp(&h2).reverse()
});
@@ -396,8 +350,8 @@ impl Scheduler {
ran_scopes.insert(scopeid);
log::debug!("about to run scope {:?}", scopeid);
if let Some(component) = self.pool.get_scope_mut(scopeid) {
if component.run_scope(&self.pool) {
if let Some(component) = self.get_scope_mut(&scopeid) {
if component.run_scope(&self) {
let (old, new) =
(component.frames.wip_head(), component.frames.fin_head());
// let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
@@ -494,7 +448,6 @@ impl Scheduler {
while self.has_any_work() {
while let Ok(Some(msg)) = self.receiver.try_next() {
match msg {
SchedulerMsg::Task(t) => todo!(),
SchedulerMsg::Immediate(im) => {
self.dirty_scopes.insert(im);
}
@@ -506,7 +459,7 @@ impl Scheduler {
// switch our priority, pop off any work
while let Some(event) = self.ui_events.pop_front() {
if let Some(scope) = self.pool.get_scope_mut(event.scope) {
if let Some(scope) = self.get_scope_mut(&event.scope) {
if let Some(element) = event.mounted_dom_id {
log::info!("Calling listener {:?}, {:?}", event.scope, element);
@@ -519,7 +472,6 @@ impl Scheduler {
self.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e),
SchedulerMsg::Task(_) => todo!(),
}
}
}
@@ -562,19 +514,19 @@ impl Scheduler {
///
/// Typically used to kickstart the VirtualDOM after initialization.
pub fn rebuild(&mut self, base_scope: ScopeId) -> Mutations {
let mut shared = self.pool.clone();
let mut shared = self.clone();
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
// TODO: drain any in-flight work
let cur_component = self
.pool
.get_scope_mut(base_scope)
.get_scope_mut(&base_scope)
.expect("The base scope should never be moved");
log::debug!("rebuild {:?}", base_scope);
// We run the component. If it succeeds, then we can diff it and add the changes to the dom.
if cur_component.run_scope(&self.pool) {
if cur_component.run_scope(&self) {
diff_machine
.stack
.create_node(cur_component.frames.fin_head(), MountType::Append);
@@ -596,13 +548,13 @@ impl Scheduler {
pub fn hard_diff(&mut self, base_scope: ScopeId) -> Mutations {
let cur_component = self
.pool
.get_scope_mut(base_scope)
.get_scope_mut(&base_scope)
.expect("The base scope should never be moved");
log::debug!("hard diff {:?}", base_scope);
if cur_component.run_scope(&self.pool) {
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self.pool);
if cur_component.run_scope(&self) {
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self);
diff_machine.cfg.force_diff = true;
diff_machine.diff_scope(base_scope);
diff_machine.mutations
@@ -611,3 +563,35 @@ impl Scheduler {
}
}
}
impl Future for Scheduler {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
let mut all_pending = true;
for fut in self.pending_futures.iter() {
let scope = self
.pool
.get_scope_mut(&fut)
.expect("Scope should never be moved");
let items = scope.items.get_mut();
for task in items.tasks.iter_mut() {
let t = task.as_mut();
let g = unsafe { Pin::new_unchecked(t) };
match g.poll(cx) {
Poll::Ready(r) => {
all_pending = false;
}
Poll::Pending => {}
}
}
}
match all_pending {
true => Poll::Pending,
false => Poll::Ready(()),
}
}
}
File renamed without changes.
+224 -333
View File
@@ -1,44 +1,36 @@
use crate::innerlude::*;
use futures_channel::mpsc::UnboundedSender;
use fxhash::FxHashMap;
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell},
collections::HashMap,
future::Future,
pin::Pin,
rc::Rc,
};
use crate::{innerlude::*, lazynodes::LazyNodes};
use bumpalo::{boxed::Box as BumpBox, Bump};
use std::ops::Deref;
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
///
/// This lets components access props, schedule updates, integrate hooks, and expose shared state.
///
/// Note: all of these methods are *imperative* - they do not act as hooks! They are meant to be used by hooks
/// to provide complex behavior. For instance, calling "add_shared_state" on every render is considered a leak. This method
/// exists for the `use_provide_state` hook to provide a shared state object.
///
/// For the most part, the only method you should be using regularly is `render`.
///
/// ## Example
///
/// ```ignore
/// #[derive(Properties)]
/// struct Props {
/// #[derive(Props)]
/// struct ExampleProps {
/// name: String
/// }
///
/// fn example(cx: Context<Props>) -> VNode {
/// html! {
/// <div> "Hello, {cx.name}" </div>
/// }
/// fn Example((cx, props): Scope<Props>) -> Element {
/// cx.render(rsx!{ div {"Hello, {props.name}"} })
/// }
/// ```
pub type Context<'a> = &'a ScopeInner;
pub type Context<'a> = &'a Scope;
/// Every component in Dioxus is represented by a `Scope`.
///
@@ -49,17 +41,33 @@ pub type Context<'a> = &'a ScopeInner;
///
/// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
/// use case they might have.
pub struct ScopeInner {
pub struct Scope {
// Book-keeping about our spot in the arena
pub(crate) parent_idx: Option<ScopeId>,
// safety:
//
// pointers to scopes are *always* valid since they are bump allocated and never freed until this scope is also freed
// this is just a bit of a hack to not need an Rc to the ScopeArena.
// todo: replace this will ScopeId and provide a connection to scope arena directly
pub(crate) parent_scope: Option<*mut Scope>,
pub(crate) our_arena_idx: ScopeId,
pub(crate) height: u32,
pub(crate) subtree: Cell<u32>,
pub(crate) is_subtree_root: Cell<bool>,
// Nodes
pub(crate) frames: ActiveFrame,
pub(crate) caller: BumpBox<'static, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>>,
pub(crate) generation: Cell<u32>,
// The double-buffering situation that we will use
pub(crate) frames: [BumpFrame; 2],
pub(crate) old_root: RefCell<Option<NodeLink>>,
pub(crate) new_root: RefCell<Option<NodeLink>>,
pub(crate) caller: *mut dyn Fn(&Scope) -> Element,
/*
we care about:
@@ -67,12 +75,7 @@ pub struct ScopeInner {
- borrowed props (and how to drop them when the parent is dropped)
- suspended nodes (and how to call their callback when their associated tasks are complete)
*/
pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
pub(crate) suspended_nodes: RefCell<FxHashMap<u64, *const VSuspended<'static>>>,
pub(crate) tasks: RefCell<Vec<BumpBox<'static, dyn Future<Output = ()>>>>,
pub(crate) pending_effects: RefCell<Vec<BumpBox<'static, dyn FnMut()>>>,
pub(crate) items: RefCell<SelfReferentialItems<'static>>,
// State
pub(crate) hooks: HookList,
@@ -80,34 +83,19 @@ pub struct ScopeInner {
// todo: move this into a centralized place - is more memory efficient
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
// whenever set_state is called, we fire off a message to the scheduler
// this closure _is_ the method called by schedule_update that marks this component as dirty
pub(crate) memoized_updater: Rc<dyn Fn()>,
pub(crate) shared: EventChannel,
pub(crate) sender: UnboundedSender<SchedulerMsg>,
}
/// Public interface for Scopes.
impl ScopeInner {
/// Get the root VNode for this Scope.
///
/// This VNode is the "entrypoint" VNode. If the component renders multiple nodes, then this VNode will be a fragment.
///
/// # Example
/// ```rust
/// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} }));
/// dom.rebuild();
///
/// let base = dom.base_scope();
///
/// if let VNode::VElement(node) = base.root_node() {
/// assert_eq!(node.tag_name, "div");
/// }
/// ```
pub fn root_node(&self) -> &VNode {
self.frames.fin_head()
}
pub struct SelfReferentialItems<'a> {
pub(crate) listeners: Vec<&'a Listener<'a>>,
pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
pub(crate) suspended_nodes: FxHashMap<u64, &'a VSuspended<'a>>,
pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
pub(crate) pending_effects: Vec<BumpBox<'a, dyn FnMut()>>,
}
// Public methods exposed to libraries and components
impl Scope {
/// Get the subtree ID that this scope belongs to.
///
/// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
@@ -128,16 +116,6 @@ impl ScopeInner {
self.subtree.get()
}
pub(crate) fn new_subtree(&self) -> Option<u32> {
if self.is_subtree_root.get() {
None
} else {
let cur = self.shared.cur_subtree.get();
self.shared.cur_subtree.set(cur + 1);
Some(cur)
}
}
/// Get the height of this Scope - IE the number of scopes above it.
///
/// A Scope with a height of `0` is the root scope - there are no other scopes above it.
@@ -173,7 +151,10 @@ impl ScopeInner {
/// assert_eq!(base.parent(), None);
/// ```
pub fn parent(&self) -> Option<ScopeId> {
self.parent_idx
match self.parent_scope {
Some(p) => Some(unsafe { &*p }.our_arena_idx),
None => None,
}
}
/// Get the ID of this Scope within this Dioxus VirtualDOM.
@@ -192,233 +173,17 @@ impl ScopeInner {
pub fn scope_id(&self) -> ScopeId {
self.our_arena_idx
}
}
// The type of closure that wraps calling components
/// The type of task that gets sent to the task scheduler
/// Submitting a fiber task returns a handle to that task, which can be used to wake up suspended nodes
pub type FiberTask = Pin<Box<dyn Future<Output = ScopeId>>>;
/// Private interface for Scopes.
impl ScopeInner {
// we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
// we are going to break this lifetime by force in order to save it on ourselves.
// To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies.
// This should never happen, but is a good check to keep around
//
// Scopes cannot be made anywhere else except for this file
// Therefore, their lifetimes are connected exclusively to the virtual dom
pub(crate) fn new(
caller: BumpBox<dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>>,
our_arena_idx: ScopeId,
parent_idx: Option<ScopeId>,
height: u32,
subtree: u32,
shared: EventChannel,
) -> Self {
let schedule_any_update = shared.schedule_any_immediate.clone();
let memoized_updater = Rc::new(move || schedule_any_update(our_arena_idx));
// wipe away the associated lifetime - we are going to manually manage the one-way lifetime graph
let caller = unsafe { std::mem::transmute(caller) };
Self {
memoized_updater,
shared,
caller,
parent_idx,
our_arena_idx,
height,
subtree: Cell::new(subtree),
is_subtree_root: Cell::new(false),
tasks: Default::default(),
frames: ActiveFrame::new(),
hooks: Default::default(),
pending_effects: Default::default(),
suspended_nodes: Default::default(),
shared_contexts: Default::default(),
listeners: Default::default(),
borrowed_props: Default::default(),
}
}
pub(crate) fn update_scope_dependencies(
&mut self,
caller: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>,
) {
log::debug!("Updating scope dependencies {:?}", self.our_arena_idx);
let caller = caller as *const _;
self.caller = unsafe { std::mem::transmute(caller) };
}
/// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
/// causing UB in our tree.
///
/// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
/// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
/// any possible references to the data in the hook list.
///
/// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
/// all listeners and borrowed props so we can clear them here.
///
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
/// be dropped.
pub(crate) fn ensure_drop_safety(&mut self, pool: &ResourcePool) {
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
// run the hooks (which hold an &mut Reference)
// right now, we don't drop
self.borrowed_props
.get_mut()
.drain(..)
.map(|li| unsafe { &*li })
.for_each(|comp| {
// First drop the component's undropped references
let scope_id = comp
.associated_scope
.get()
.expect("VComponents should be associated with a valid Scope");
if let Some(scope) = pool.get_scope_mut(scope_id) {
scope.ensure_drop_safety(pool);
let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
drop_props();
}
});
// Now that all the references are gone, we can safely drop our own references in our listeners.
self.listeners
.get_mut()
.drain(..)
.map(|li| unsafe { &*li })
.for_each(|listener| drop(listener.callback.borrow_mut().take()));
}
/// A safe wrapper around calling listeners
pub(crate) fn call_listener(&mut self, event: UserEvent, element: ElementId) {
let listners = self.listeners.borrow_mut();
let raw_listener = listners.iter().find(|lis| {
let search = unsafe { &***lis };
if search.event == event.name {
let search_id = search.mounted_node.get();
search_id.map(|f| f == element).unwrap_or(false)
} else {
false
}
});
if let Some(raw_listener) = raw_listener {
let listener = unsafe { &**raw_listener };
let mut cb = listener.callback.borrow_mut();
if let Some(cb) = cb.as_mut() {
(cb)(event.event);
}
} else {
log::warn!("An event was triggered but there was no listener to handle it");
}
}
/*
General strategy here is to load up the appropriate suspended task and then run it.
Suspended nodes cannot be called repeatedly.
*/
pub(crate) fn call_suspended_node<'a>(&'a mut self, task_id: u64) {
let mut nodes = self.suspended_nodes.borrow_mut();
if let Some(suspended) = nodes.remove(&task_id) {
let sus: &'a VSuspended<'static> = unsafe { &*suspended };
let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
let mut boxed = sus.callback.borrow_mut().take().unwrap();
let new_node: Element<'a> = boxed();
}
}
// run the list of effects
pub(crate) fn run_effects(&mut self, pool: &ResourcePool) {
todo!()
// let mut effects = self.frames.effects.borrow_mut();
// let mut effects = effects.drain(..).collect::<Vec<_>>();
// for effect in effects {
// let effect = unsafe { &*effect };
// let effect = effect.as_ref();
// let mut effect = effect.borrow_mut();
// let mut effect = effect.as_mut();
// effect.run(pool);
// }
}
/// Render this component.
///
/// Returns true if the scope completed successfully and false if running failed (IE a None error was propagated).
pub(crate) fn run_scope<'sel>(&'sel mut self, pool: &ResourcePool) -> bool {
// Cycle to the next frame and then reset it
// This breaks any latent references, invalidating every pointer referencing into it.
// Remove all the outdated listeners
self.ensure_drop_safety(pool);
// Safety:
// - We dropped the listeners, so no more &mut T can be used while these are held
// - All children nodes that rely on &mut T are replaced with a new reference
unsafe { self.hooks.reset() };
// Safety:
// - We've dropped all references to the wip bump frame
unsafe { self.frames.reset_wip_frame() };
// just forget about our suspended nodes while we're at it
self.suspended_nodes.get_mut().clear();
// guarantee that we haven't screwed up - there should be no latent references anywhere
debug_assert!(self.listeners.borrow().is_empty());
debug_assert!(self.suspended_nodes.borrow().is_empty());
debug_assert!(self.borrowed_props.borrow().is_empty());
log::debug!("Borrowed stuff is successfully cleared");
// Cast the caller ptr from static to one with our own reference
let render: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = unsafe { &*self.caller };
// Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
if let Some(builder) = render(self) {
let new_head = builder.into_vnode(NodeFactory {
bump: &self.frames.wip_frame().bump,
});
log::debug!("Render is successful");
// the user's component succeeded. We can safely cycle to the next frame
self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
self.frames.cycle_frame();
true
} else {
false
}
}
/// Create a subscription that schedules a future render for the reference component
///
/// ## Notice: you should prefer using prepare_update and get_scope_id
pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
self.memoized_updater.clone()
}
/// Get the [`ScopeId`] of a mounted component.
///
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
pub fn needs_update(&self) {
(self.memoized_updater)()
}
/// Get the [`ScopeId`] of a mounted component.
///
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
pub fn needs_update_any(&self, id: ScopeId) {
(self.shared.schedule_any_immediate)(id)
// pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
let chan = self.sender.clone();
let id = self.scope_id();
Rc::new(move || {
let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
})
}
/// Schedule an update for any component given its ScopeId.
@@ -427,15 +192,31 @@ impl ScopeInner {
///
/// This method should be used when you want to schedule an update for a component
pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
self.shared.schedule_any_immediate.clone()
let chan = self.sender.clone();
Rc::new(move |id| {
let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
})
}
/// Get the [`ScopeId`] of a mounted component.
///
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
pub fn needs_update(&self) {
self.needs_update_any(self.scope_id())
}
/// Get the [`ScopeId`] of a mounted component.
///
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
pub fn needs_update_any(&self, id: ScopeId) {
let _ = self.sender.unbounded_send(SchedulerMsg::Immediate(id));
}
/// Get the [`ScopeId`] of a mounted component.
///
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
pub fn bump(&self) -> &Bump {
let bump = &self.frames.wip_frame().bump;
bump
&self.wip_frame().bump
}
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
@@ -445,21 +226,28 @@ impl ScopeInner {
/// ## Example
///
/// ```ignore
/// fn Component(cx: Context<()>) -> VNode {
/// fn Component(cx: Scope, props: &Props) -> Element {
/// // Lazy assemble the VNode tree
/// let lazy_tree = html! {<div> "Hello World" </div>};
/// let lazy_nodes = rsx!("hello world");
///
/// // Actually build the tree and allocate it
/// cx.render(lazy_tree)
/// }
///```
pub fn render<'src>(
&'src self,
lazy_nodes: Option<LazyNodes<'src, '_>>,
) -> Option<VNode<'src>> {
let bump = &self.frames.wip_frame().bump;
pub fn render<'src>(&'src self, lazy_nodes: Option<LazyNodes<'src, '_>>) -> Option<NodeLink> {
let bump = &self.wip_frame().bump;
let factory = NodeFactory { bump };
lazy_nodes.map(|f| f.call(factory))
let node = lazy_nodes.map(|f| f.call(factory))?;
let idx = self
.wip_frame()
.add_node(unsafe { std::mem::transmute(node) });
Some(NodeLink {
gen_id: self.generation.get(),
scope_id: self.our_arena_idx,
link_idx: idx,
})
}
/// Push an effect to be ran after the component has been successfully mounted to the dom
@@ -475,8 +263,9 @@ impl ScopeInner {
// erase the 'src lifetime for self-referential storage
let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
self.pending_effects.borrow_mut().push(self_ref_fut);
self.pending_effects.borrow().len() - 1
let mut items = self.items.borrow_mut();
items.pending_effects.push(self_ref_fut);
items.pending_effects.len() - 1
}
/// Pushes the future onto the poll queue to be polled
@@ -491,8 +280,9 @@ impl ScopeInner {
// erase the 'src lifetime for self-referential storage
let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
self.tasks.borrow_mut().push(self_ref_fut);
self.tasks.borrow().len() - 1
let mut items = self.items.borrow_mut();
items.tasks.push(self_ref_fut);
items.tasks.len() - 1
}
/// This method enables the ability to expose state to children further down the VirtualDOM Tree.
@@ -519,7 +309,7 @@ impl ScopeInner {
/// rsx!(cx, div { "hello {state.0}" })
/// }
/// ```
pub fn provide_state<T>(self, value: T)
pub fn provide_state<T>(&self, value: T)
where
T: 'static,
{
@@ -531,11 +321,21 @@ impl ScopeInner {
}
/// Try to retrieve a SharedState with type T from the any parent Scope.
pub fn consume_state<T: 'static>(self) -> Option<Rc<T>> {
let getter = &self.shared.get_shared_context;
let ty = TypeId::of::<T>();
let idx = self.our_arena_idx;
getter(idx, ty).map(|f| f.downcast().unwrap())
pub fn consume_state<T: 'static>(&self) -> Option<Rc<T>> {
if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
Some(shared.clone().downcast::<T>().unwrap())
} else {
let mut search_parent = self.parent_scope;
while let Some(parent_ptr) = search_parent {
let parent = unsafe { &*parent_ptr };
if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
return Some(shared.clone().downcast::<T>().unwrap());
}
search_parent = parent.parent_scope;
}
None
}
}
/// Create a new subtree with this scope as the root of the subtree.
@@ -553,7 +353,7 @@ impl ScopeInner {
/// rsx!(cx, div { "Subtree {id}"})
/// };
/// ```
pub fn create_subtree(self) -> Option<u32> {
pub fn create_subtree(&self) -> Option<u32> {
self.new_subtree()
}
@@ -570,7 +370,7 @@ impl ScopeInner {
/// rsx!(cx, div { "Subtree {id}"})
/// };
/// ```
pub fn get_current_subtree(self) -> u32 {
pub fn get_current_subtree(&self) -> u32 {
self.subtree()
}
@@ -580,8 +380,9 @@ impl ScopeInner {
///
/// - Initializer: closure used to create the initial hook state
/// - Runner: closure used to output a value every time the hook is used
/// - Cleanup: closure used to teardown the hook once the dom is cleaned up
///
/// To "cleanup" the hook, implement `Drop` on the stored hook value. Whenever the component is dropped, the hook
/// will be dropped as well.
///
/// # Example
///
@@ -591,42 +392,132 @@ impl ScopeInner {
/// use_hook(
/// || Rc::new(RefCell::new(initial_value())),
/// |state| state,
/// |_| {},
/// )
/// }
/// ```
pub fn use_hook<'src, State, Output, Init, Run, Cleanup>(
pub fn use_hook<'src, State: 'static, Output: 'src>(
&'src self,
initializer: Init,
runner: Run,
cleanup: Cleanup,
) -> Output
where
State: 'static,
Output: 'src,
Init: FnOnce(usize) -> State,
Run: FnOnce(&'src mut State) -> Output,
Cleanup: FnOnce(Box<State>) + 'static,
{
// If the idx is the same as the hook length, then we need to add the current hook
initializer: impl FnOnce(usize) -> State,
runner: impl FnOnce(&'src mut State) -> Output,
) -> Output {
if self.hooks.at_end() {
self.hooks.push_hook(
initializer(self.hooks.len()),
Box::new(|raw| {
let s = raw.downcast::<State>().unwrap();
cleanup(s);
}),
);
self.hooks.push_hook(initializer(self.hooks.len()));
}
runner(self.hooks.next::<State>().expect(HOOK_ERR_MSG))
}
}
const HOOK_ERR_MSG: &str = r###"
const HOOK_ERR_MSG: &str = r###"
Unable to retrieve the hook that was initialized at this index.
Consult the `rules of hooks` to understand how to use hooks properly.
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
Functions prefixed with "use" should never be called conditionally.
"###;
runner(self.hooks.next::<State>().expect(HOOK_ERR_MSG))
}
}
// Important internal methods
impl Scope {
/// The "work in progress frame" represents the frame that is currently being worked on.
pub(crate) fn wip_frame(&self) -> &BumpFrame {
match self.generation.get() & 1 == 0 {
true => &self.frames[0],
false => &self.frames[1],
}
}
pub(crate) fn fin_frame(&self) -> &BumpFrame {
match self.generation.get() & 1 == 1 {
true => &self.frames[0],
false => &self.frames[1],
}
}
pub unsafe fn reset_wip_frame(&self) {
// todo: unsafecell or something
let bump = self.wip_frame() as *const _ as *mut Bump;
let g = &mut *bump;
g.reset();
}
pub fn cycle_frame(&self) {
self.generation.set(self.generation.get() + 1);
}
/// A safe wrapper around calling listeners
pub(crate) fn call_listener(&self, event: UserEvent, element: ElementId) {
let listners = &mut self.items.borrow_mut().listeners;
let listener = listners.iter().find(|lis| {
let search = lis;
if search.event == event.name {
let search_id = search.mounted_node.get();
search_id.map(|f| f == element).unwrap_or(false)
} else {
false
}
});
if let Some(listener) = listener {
let mut cb = listener.callback.borrow_mut();
if let Some(cb) = cb.as_mut() {
(cb)(event.event);
}
} else {
log::warn!("An event was triggered but there was no listener to handle it");
}
}
// General strategy here is to load up the appropriate suspended task and then run it.
// Suspended nodes cannot be called repeatedly.
pub(crate) fn call_suspended_node<'a>(&'a mut self, task_id: u64) {
let mut nodes = &mut self.items.get_mut().suspended_nodes;
if let Some(suspended) = nodes.remove(&task_id) {
let sus: &'a VSuspended<'static> = unsafe { &*suspended };
let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
let mut boxed = sus.callback.borrow_mut().take().unwrap();
let new_node: Element = boxed();
}
}
// run the list of effects
pub(crate) fn run_effects(&mut self) {
for mut effect in self.items.get_mut().pending_effects.drain(..) {
effect();
}
}
pub(crate) fn new_subtree(&self) -> Option<u32> {
todo!()
// if self.is_subtree_root.get() {
// None
// } else {
// let cur = self.shared.cur_subtree.get();
// self.shared.cur_subtree.set(cur + 1);
// Some(cur)
// }
}
}
pub struct BumpFrame {
pub bump: Bump,
pub nodes: RefCell<Vec<VNode<'static>>>,
}
impl BumpFrame {
pub fn new() -> Self {
let bump = Bump::new();
let node = &*bump.alloc(VText {
text: "asd",
dom_id: Default::default(),
is_static: false,
});
let nodes = RefCell::new(vec![VNode::Text(unsafe { std::mem::transmute(node) })]);
Self { bump, nodes }
}
fn add_node(&self, node: VNode<'static>) -> usize {
let mut nodes = self.nodes.borrow_mut();
nodes.push(node);
nodes.len() - 1
}
}
+290
View File
@@ -0,0 +1,290 @@
use slab::Slab;
use std::{
borrow::BorrowMut,
cell::{Cell, RefCell},
};
use bumpalo::{boxed::Box as BumpBox, Bump};
use futures_channel::mpsc::UnboundedSender;
use crate::innerlude::*;
pub type FcSlot = *const ();
// pub heuristics: FxHashMap<FcSlot, Heuristic>,
pub struct Heuristic {
hook_arena_size: usize,
node_arena_size: usize,
}
// a slab-like arena with stable references even when new scopes are allocated
// uses a bump arena as a backing
//
// has an internal heuristics engine to pre-allocate arenas to the right size
pub(crate) struct ScopeArena {
bump: Bump,
scopes: Vec<*mut Scope>,
free_scopes: Vec<ScopeId>,
nodes: RefCell<Slab<*const VNode<'static>>>,
pub(crate) sender: UnboundedSender<SchedulerMsg>,
}
impl ScopeArena {
pub fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
Self {
bump: Bump::new(),
scopes: Vec::new(),
free_scopes: Vec::new(),
nodes: RefCell::new(Slab::new()),
sender,
}
}
pub fn get_scope(&self, id: &ScopeId) -> Option<&Scope> {
unsafe { Some(&*self.scopes[id.0]) }
}
pub fn new_with_key(
&mut self,
fc_ptr: *const (),
caller: *mut dyn Fn(&Scope) -> Element,
parent_scope: Option<*mut Scope>,
height: u32,
subtree: u32,
) -> ScopeId {
if let Some(id) = self.free_scopes.pop() {
// have already called drop on it - the slot is still chillin tho
let scope = unsafe { &mut *self.scopes[id.0 as usize] };
todo!("override the scope contents");
id
} else {
let scope_id = ScopeId(self.scopes.len());
let old_root = NodeLink {
link_idx: 0,
gen_id: 0,
scope_id,
};
let new_root = NodeLink {
link_idx: 0,
gen_id: 0,
scope_id,
};
let new_scope = Scope {
sender: self.sender.clone(),
parent_scope,
our_arena_idx: scope_id,
height,
subtree: Cell::new(subtree),
is_subtree_root: Cell::new(false),
frames: [BumpFrame::new(), BumpFrame::new()],
hooks: Default::default(),
shared_contexts: Default::default(),
caller,
generation: 0.into(),
old_root: RefCell::new(Some(old_root)),
new_root: RefCell::new(Some(new_root)),
// old_root: RefCell::new(Some(old_root)),
// new_root: RefCell::new(None),
items: RefCell::new(SelfReferentialItems {
listeners: Default::default(),
borrowed_props: Default::default(),
suspended_nodes: Default::default(),
tasks: Default::default(),
pending_effects: Default::default(),
}),
};
let stable = self.bump.alloc(new_scope);
self.scopes.push(stable);
scope_id
}
}
pub fn try_remove(&self, id: &ScopeId) -> Option<Scope> {
todo!()
}
pub fn reserve_node(&self, node: &VNode) -> ElementId {
let mut els = self.nodes.borrow_mut();
let entry = els.vacant_entry();
let key = entry.key();
let id = ElementId(key);
let node = node as *const _;
let node = unsafe { std::mem::transmute(node) };
entry.insert(node);
id
// let nodes = self.nodes.borrow_mut();
// let id = nodes.insert(());
// let node_id = ElementId(id);
// node = Some(node_id);
// node_id
}
pub fn collect_garbage(&self, id: ElementId) {
todo!()
}
// These methods would normally exist on `scope` but they need access to *all* of the scopes
/// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
/// causing UB in our tree.
///
/// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
/// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
/// any possible references to the data in the hook list.
///
/// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
/// all listeners and borrowed props so we can clear them here.
///
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
/// be dropped.
pub(crate) fn ensure_drop_safety(&self, scope_id: &ScopeId) {
let scope = self.get_scope(scope_id).unwrap();
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
// run the hooks (which hold an &mut Reference)
// right now, we don't drop
scope
.items
.borrow_mut()
.borrowed_props
.drain(..)
.for_each(|comp| {
// First drop the component's undropped references
let scope_id = comp
.associated_scope
.get()
.expect("VComponents should be associated with a valid Scope");
todo!("move this onto virtualdom");
// let scope = unsafe { &mut *scope_id };
// scope.ensure_drop_safety();
todo!("drop the component's props");
// let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
// drop_props();
});
// Now that all the references are gone, we can safely drop our own references in our listeners.
scope
.items
.borrow_mut()
.listeners
.drain(..)
.map(|li| unsafe { &*li })
.for_each(|listener| drop(listener.callback.borrow_mut().take()));
}
pub(crate) fn run_scope(&self, id: &ScopeId) -> bool {
let scope = self
.get_scope(id)
.expect("The base scope should never be moved");
// Cycle to the next frame and then reset it
// This breaks any latent references, invalidating every pointer referencing into it.
// Remove all the outdated listeners
self.ensure_drop_safety(id);
// Safety:
// - We dropped the listeners, so no more &mut T can be used while these are held
// - All children nodes that rely on &mut T are replaced with a new reference
unsafe { scope.hooks.reset() };
// Safety:
// - We've dropped all references to the wip bump frame with "ensure_drop_safety"
unsafe { scope.reset_wip_frame() };
{
let mut items = scope.items.borrow_mut();
// just forget about our suspended nodes while we're at it
items.suspended_nodes.clear();
// guarantee that we haven't screwed up - there should be no latent references anywhere
debug_assert!(items.listeners.is_empty());
debug_assert!(items.suspended_nodes.is_empty());
debug_assert!(items.borrowed_props.is_empty());
log::debug!("Borrowed stuff is successfully cleared");
// temporarily cast the vcomponent to the right lifetime
// let vcomp = scope.load_vcomp();
}
let render: &dyn Fn(&Scope) -> Element = unsafe { &*scope.caller };
// Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
scope.wip_frame().nodes.borrow_mut().clear();
if let Some(key) = render(scope) {
dbg!(key);
dbg!(&scope.wip_frame().nodes.borrow_mut());
// let mut old = scope.old_root.borrow_mut();
// let mut new = scope.new_root.borrow_mut();
// let new_old = new.clone();
// *old = new_old;
// *new = Some(key);
// dbg!(&old);
// dbg!(&new);
// the user's component succeeded. We can safely cycle to the next frame
// scope.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
scope.cycle_frame();
true
} else {
false
}
}
pub fn wip_head(&self, id: &ScopeId) -> &VNode {
let scope = self.get_scope(id).unwrap();
let wip_frame = scope.wip_frame();
let nodes = wip_frame.nodes.borrow();
let node = nodes.get(0).unwrap();
unsafe { std::mem::transmute(node) }
// let root = scope.old_root.borrow();
// let link = root.as_ref().unwrap();
// dbg!(link);
// // for now, components cannot pass portals through each other
// assert_eq!(link.scope_id, *id);
// // assert_eq!(link.gen_id, scope.generation.get() - 1);
// // let items = scope.items.borrow();
// let nodes = scope.wip_frame().nodes.borrow();
// let node = nodes.get(link.link_idx).unwrap();
// unsafe { std::mem::transmute(node) }
}
pub fn fin_head(&self, id: &ScopeId) -> &VNode {
let scope = self.get_scope(id).unwrap();
let wip_frame = scope.fin_frame();
let nodes = wip_frame.nodes.borrow();
let node = nodes.get(0).unwrap();
unsafe { std::mem::transmute(node) }
// let scope = self.get_scope(id).unwrap();
// let root = scope.new_root.borrow();
// let link = root.as_ref().unwrap();
// // for now, components cannot pass portals through each other
// assert_eq!(link.scope_id, *id);
// // assert_eq!(link.gen_id, scope.generation.get());
// let nodes = scope.fin_frame().nodes.borrow();
// let node = nodes.get(link.link_idx).unwrap();
// unsafe { std::mem::transmute(node) }
}
pub fn root_node(&self, id: &ScopeId) -> &VNode {
self.wip_head(id)
}
}
-39
View File
@@ -1,39 +0,0 @@
use crate::innerlude::*;
use futures_channel::mpsc::UnboundedSender;
pub struct TaskHandle {
pub(crate) sender: UnboundedSender<SchedulerMsg>,
pub(crate) our_id: u64,
}
impl TaskHandle {
/// Toggles this coroutine off/on.
///
/// This method is not synchronous - your task will not stop immediately.
pub fn toggle(&self) {
self.sender
.unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
.unwrap()
}
/// This method is not synchronous - your task will not stop immediately.
pub fn resume(&self) {
self.sender
.unbounded_send(SchedulerMsg::Task(TaskMsg::ResumeTask(self.our_id)))
.unwrap()
}
/// This method is not synchronous - your task will not stop immediately.
pub fn stop(&self) {
self.sender
.unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
.unwrap()
}
/// This method is not synchronous - your task will not stop immediately.
pub fn restart(&self) {
self.sender
.unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
.unwrap()
}
}
+26 -15
View File
@@ -6,15 +6,16 @@ use crate::nodes::IntoVNode;
pub struct TestDom {
bump: Bump,
scheduler: Scheduler,
// scheduler: Scheduler,
}
impl TestDom {
pub fn new() -> TestDom {
let bump = Bump::new();
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
let scheduler = Scheduler::new(sender, receiver);
TestDom { bump, scheduler }
todo!()
// let scheduler = Scheduler::new(sender, receiver, 10, 100);
// TestDom { bump, scheduler }
}
pub fn new_factory(&self) -> NodeFactory {
@@ -32,7 +33,9 @@ impl TestDom {
pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
let mutations = Mutations::new();
let mut machine = DiffMachine::new(mutations, &self.scheduler.pool);
let mut machine: DiffState = todo!();
// let mut machine = DiffState::new(mutations);
// let mut machine = DiffState::new(mutations);
machine.stack.push(DiffInstruction::Diff { new, old });
machine.mutations
}
@@ -40,13 +43,17 @@ impl TestDom {
pub fn create<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
let old = self.bump.alloc(self.render_direct(left));
let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
let mut machine: DiffState = todo!();
// let mut machine = DiffState::new(Mutations::new());
// let mut machine = DiffState::new(Mutations::new());
machine.stack.create_node(old, MountType::Append);
machine.work(&mut || false);
todo!()
machine.mutations
// machine.work(&mut || false);
// machine.mutations
}
pub fn lazy_diff<'a>(
@@ -56,22 +63,26 @@ impl TestDom {
) -> (Mutations<'a>, Mutations<'a>) {
let (old, new) = (self.render(left), self.render(right));
let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
let mut machine: DiffState = todo!();
// let mut machine = DiffState::new(Mutations::new());
machine.stack.create_node(old, MountType::Append);
machine.work(|| false);
let create_edits = machine.mutations;
todo!()
let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool);
// machine.work(|| false);
// let create_edits = machine.mutations;
machine.stack.push(DiffInstruction::Diff { old, new });
// let mut machine: DiffState = todo!();
// // let mut machine = DiffState::new(Mutations::new());
machine.work(&mut || false);
// machine.stack.push(DiffInstruction::Diff { old, new });
let edits = machine.mutations;
// machine.work(&mut || false);
(create_edits, edits)
// let edits = machine.mutations;
// (create_edits, edits)
}
}
-57
View File
@@ -7,63 +7,6 @@ pub fn empty_cell() -> Cell<Option<ElementId>> {
Cell::new(None)
}
pub fn type_name_of<T>(_: T) -> &'static str {
std::any::type_name::<T>()
}
use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};
// use crate::task::{Context, Poll};
/// Cooperatively gives up a timeslice to the task scheduler.
///
/// Calling this function will move the currently executing future to the back
/// of the execution queue, making room for other futures to execute. This is
/// especially useful after running CPU-intensive operations inside a future.
///
/// See also [`task::spawn_blocking`].
///
/// [`task::spawn_blocking`]: fn.spawn_blocking.html
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// # async_std::task::block_on(async {
/// #
/// use async_std::task;
///
/// task::yield_now().await;
/// #
/// # })
/// ```
#[inline]
pub async fn yield_now() {
YieldNow(false).await
}
struct YieldNow(bool);
impl Future for YieldNow {
type Output = ();
// The futures executor is implemented as a FIFO queue, so all this future
// does is re-schedule the future back to the end of the queue, giving room
// for other futures to progress.
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
if !self.0 {
self.0 = true;
cx.waker().wake_by_ref();
Poll::Pending
} else {
Poll::Ready(())
}
}
}
/// A component's unique identifier.
///
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
+367 -170
View File
@@ -19,10 +19,14 @@
//! This module includes just the barebones for a complete VirtualDOM API.
//! Additional functionality is defined in the respective files.
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use crate::innerlude::*;
use std::{any::Any, rc::Rc};
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
use futures_util::{Future, StreamExt};
use fxhash::FxHashSet;
use indexmap::IndexSet;
use std::pin::Pin;
use std::task::Poll;
use std::{any::Any, collections::VecDeque};
/// An integrated virtual node system that progresses events and diffs UI trees.
///
@@ -55,18 +59,22 @@ use std::{any::Any, rc::Rc};
/// }
/// ```
pub struct VirtualDom {
scheduler: Scheduler,
base_scope: ScopeId,
root_fc: Box<dyn Any>,
_root_caller: *mut dyn Fn(&Scope) -> Element,
root_props: Rc<dyn Any>,
pub(crate) scopes: ScopeArena,
// we need to keep the allocation around, but we don't necessarily use it
_root_caller: Box<dyn Any>,
receiver: UnboundedReceiver<SchedulerMsg>,
pub(crate) sender: UnboundedSender<SchedulerMsg>,
// Every component that has futures that need to be polled
pending_futures: FxHashSet<ScopeId>,
pending_messages: VecDeque<SchedulerMsg>,
dirty_scopes: IndexSet<ScopeId>,
}
// Methods to create the VirtualDom
impl VirtualDom {
/// Create a new VirtualDOM with a component that does not have special props.
///
@@ -127,7 +135,7 @@ impl VirtualDom {
Self::new_with_props_and_scheduler(root, root_props, sender, receiver)
}
/// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler.
/// 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.
@@ -137,100 +145,283 @@ impl VirtualDom {
sender: UnboundedSender<SchedulerMsg>,
receiver: UnboundedReceiver<SchedulerMsg>,
) -> Self {
let root_fc = Box::new(root);
let mut scopes = ScopeArena::new(sender.clone());
let root_props: Rc<dyn Any> = Rc::new(root_props);
let props = root_props.clone();
let root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
Box::new(move |scope: &ScopeInner| {
let props = props.downcast_ref::<P>().unwrap();
let node = root((scope, props));
// cast into the right lifetime
unsafe { std::mem::transmute(node) }
});
let caller = unsafe { bumpalo::boxed::Box::from_raw(root_caller.as_mut() as *mut _) };
let scheduler = Scheduler::new(sender, receiver);
let base_scope = scheduler.pool.insert_scope_with_key(|myidx| {
ScopeInner::new(caller, myidx, None, 0, 0, scheduler.pool.channel.clone())
});
let caller = Box::new(move |f: &Scope| -> Element { root(f, &root_props) });
let caller_ref: *mut dyn Fn(&Scope) -> Element = Box::into_raw(caller);
let base_scope = scopes.new_with_key(root as _, caller_ref, None, 0, 0);
Self {
_root_caller: Box::new(root_caller),
root_fc,
scopes,
base_scope,
scheduler,
root_props,
receiver,
// todo: clean this up manually?
_root_caller: caller_ref,
pending_messages: VecDeque::new(),
pending_futures: Default::default(),
dirty_scopes: Default::default(),
sender,
}
}
/// Get the [`Scope`] for the root component.
/// Get the [`ScopeState`] for the root component.
///
/// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
/// directly.
pub fn base_scope(&self) -> &ScopeInner {
self.scheduler.pool.get_scope(self.base_scope).unwrap()
///
/// # Example
pub fn base_scope(&self) -> &Scope {
self.get_scope(&self.base_scope).unwrap()
}
/// Get the [`Scope`] for a component given its [`ScopeId`]
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeInner> {
self.scheduler.pool.get_scope(id)
/// Get the [`ScopeState`] for a component given its [`ScopeId`]
///
/// # Example
///
///
///
pub fn get_scope<'a>(&'a self, id: &ScopeId) -> Option<&'a Scope> {
self.scopes.get_scope(id)
}
/// Update the root props of this VirtualDOM.
/// Get an [`UnboundedSender`] handle to the channel used by the scheduler.
///
/// This method returns None if the old props could not be removed. The entire VirtualDOM will be rebuilt immediately,
/// so calling this method will block the main thread until computation is done.
/// # Example
///
/// ## Example
///
/// ```rust
/// #[derive(Props, PartialEq)]
/// struct AppProps {
/// route: &'static str
/// }
/// static App: FC<AppProps> = |(cx, props)|cx.render(rsx!{ "route is {cx.route}" });
///
pub fn get_scheduler_channel(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
self.sender.clone()
}
/// Check if the [`VirtualDom`] has any pending updates or work to be done.
///
/// let mut dom = VirtualDom::new_with_props(App, AppProps { route: "start" });
/// # Example
///
/// let mutations = dom.update_root_props(AppProps { route: "end" }).unwrap();
/// ```
pub fn update_root_props<P>(&mut self, root_props: P) -> Option<Mutations>
where
P: 'static,
{
let root_scope = self.scheduler.pool.get_scope_mut(self.base_scope).unwrap();
///
///
pub fn has_any_work(&self) -> bool {
!(self.dirty_scopes.is_empty() && self.pending_messages.is_empty())
}
// Pre-emptively drop any downstream references of the old props
root_scope.ensure_drop_safety(&self.scheduler.pool);
/// Waits for the scheduler to have work
/// This lets us poll async tasks during idle periods without blocking the main thread.
pub async fn wait_for_work(&mut self) {
// todo: poll the events once even if there is work to do to prevent starvation
let mut root_props: Rc<dyn Any> = Rc::new(root_props);
if let Some(props_ptr) = root_props.downcast_ref::<P>().map(|p| p as *const P) {
// Swap the old props and new props
std::mem::swap(&mut self.root_props, &mut root_props);
let root = *self.root_fc.downcast_ref::<FC<P>>().unwrap();
let root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
Box::new(move |scope: &ScopeInner| unsafe {
let props: &'_ P = &*(props_ptr as *const P);
std::mem::transmute(root((scope, props)))
});
root_scope.update_scope_dependencies(&root_caller);
drop(root_props);
Some(self.rebuild())
// if there's no futures in the virtualdom, just wait for a scheduler message and put it into the queue to be processed
if self.pending_futures.is_empty() {
self.pending_messages
.push_front(self.receiver.next().await.unwrap());
} else {
None
struct PollTasks<'a> {
pending_futures: &'a FxHashSet<ScopeId>,
scopes: &'a ScopeArena,
}
impl<'a> Future for PollTasks<'a> {
type Output = ();
fn poll(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Self::Output> {
let mut all_pending = true;
// Poll every scope manually
for fut in self.pending_futures.iter() {
let scope = self
.scopes
.get_scope(fut)
.expect("Scope should never be moved");
let mut items = scope.items.borrow_mut();
for task in items.tasks.iter_mut() {
let task = task.as_mut();
// todo: does this make sense?
// I don't usually write futures by hand
// I think the futures neeed to be pinned using bumpbox or something
// right now, they're bump allocated so this shouldn't matter anyway - they're not going to move
let unpinned = unsafe { Pin::new_unchecked(task) };
if unpinned.poll(cx).is_ready() {
all_pending = false
}
}
}
// Resolve the future if any singular task is ready
match all_pending {
true => Poll::Pending,
false => Poll::Ready(()),
}
}
}
// Poll both the futures and the scheduler message queue simulataneously
use futures_util::future::{select, Either};
let scheduler_fut = self.receiver.next();
let tasks_fut = PollTasks {
pending_futures: &self.pending_futures,
scopes: &self.scopes,
};
match select(tasks_fut, scheduler_fut).await {
// Futures don't generate work
Either::Left((_, _)) => {}
// Save these messages in FIFO to be processed later
Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
}
}
}
/// Run the virtualdom with a deadline.
///
/// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
/// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
/// exhaust the deadline working on them.
///
/// 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.
///
/// Due to platform differences in how time is handled, this method accepts a future that resolves when the deadline
/// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
/// deadline closure manually.
///
/// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
/// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
/// the screen will "jank" up. In debug, this will trigger an alert.
///
/// If there are no in-flight fibers when this method is called, it will await any possible tasks, aborting early if
/// the provided deadline future resolves.
///
/// 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.
///
/// # Example
///
/// ```no_run
/// static App: FC<()> = |(cx, props)|rsx!(cx, 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);
/// }
/// ```
///
/// ## 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 fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
let mut committed_mutations = vec![];
while self.has_any_work() {
while let Ok(Some(msg)) = self.receiver.try_next() {
self.pending_messages.push_front(msg);
}
while let Some(msg) = self.pending_messages.pop_back() {
match msg {
SchedulerMsg::Immediate(id) => {
self.dirty_scopes.insert(id);
}
SchedulerMsg::UiEvent(event) => {
if let Some(element) = event.mounted_dom_id {
log::info!("Calling listener {:?}, {:?}", event.scope_id, element);
let scope = self.scopes.get_scope(&event.scope_id).unwrap();
// TODO: bubble properly here
scope.call_listener(event, element);
while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
self.pending_messages.push_front(dirty_scope);
}
} else {
log::debug!("User event without a targetted ElementId. Not currently supported.\nUnsure how to proceed. {:?}", event);
}
}
}
}
let mut diff_state: DiffState = DiffState::new(Mutations::new());
let mut ran_scopes = FxHashSet::default();
// todo: the 2021 version of rust will let us not have to force the borrow
let scopes = &self.scopes;
// 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() {
log::info!("handling dirty scope {:?}", scopeid);
if !ran_scopes.contains(&scopeid) {
ran_scopes.insert(scopeid);
log::debug!("about to run scope {:?}", scopeid);
if self.scopes.run_scope(&scopeid) {
let (old, new) = (
self.scopes.wip_head(&scopeid),
self.scopes.fin_head(&scopeid),
);
diff_state.stack.scope_stack.push(scopeid);
diff_state.stack.push(DiffInstruction::Diff { new, old });
}
}
}
let work_completed = self.scopes.work(&mut diff_state, &mut deadline);
if work_completed {
let DiffState {
mutations,
seen_scopes,
stack,
..
} = diff_state;
for scope in seen_scopes {
self.dirty_scopes.remove(&scope);
}
// I think the stack should be empty at the end of diffing?
debug_assert_eq!(stack.scope_stack.len(), 0);
committed_mutations.push(mutations);
} else {
// leave the work in an incomplete state
log::debug!("don't have a mechanism to pause work (yet)");
return committed_mutations;
}
}
committed_mutations
}
/// 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.
@@ -242,14 +433,27 @@ impl VirtualDom {
///
/// # Example
/// ```
/// static App: FC<()> = |(cx, props)|cx.render(rsx!{ "hello world" });
/// static App: FC<()> = |(cx, props)| cx.render(rsx!{ "hello world" });
/// let mut dom = VirtualDom::new();
/// let edits = dom.rebuild();
///
/// apply_edits(edits);
/// ```
pub fn rebuild(&mut self) -> Mutations {
self.scheduler.rebuild(self.base_scope)
let mut diff_machine = DiffState::new(Mutations::new());
let scope_id = self.base_scope;
if self.scopes.run_scope(&scope_id) {
diff_machine
.stack
.create_node(self.scopes.fin_head(&scope_id), MountType::Append);
diff_machine.stack.scope_stack.push(scope_id);
self.scopes.work(&mut diff_machine, || false);
}
diff_machine.mutations
}
/// Compute a manual diff of the VirtualDOM between states.
@@ -286,107 +490,100 @@ impl VirtualDom {
///
/// let edits = dom.diff();
/// ```
pub fn diff(&mut self) -> Mutations {
self.scheduler.hard_diff(self.base_scope)
}
pub fn hard_diff<'a>(&'a mut self, scope_id: &ScopeId) -> Option<Mutations<'a>> {
log::debug!("hard diff {:?}", scope_id);
/// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
///
/// This method will not wait for any suspended nodes to complete. If there is no pending work, then this method will
/// return "None"
pub fn run_immediate(&mut self) -> Option<Vec<Mutations>> {
if self.scheduler.has_any_work() {
Some(self.scheduler.work_sync())
if self.scopes.run_scope(scope_id) {
let mut diff_machine = DiffState::new(Mutations::new());
diff_machine.force_diff = true;
self.scopes.diff_scope(&mut diff_machine, scope_id);
dbg!(&diff_machine.mutations);
Some(diff_machine.mutations)
} else {
None
}
}
}
/// Run the virtualdom with a deadline.
pub enum SchedulerMsg {
// events from the host
UiEvent(UserEvent),
// setstate
Immediate(ScopeId),
}
#[derive(Debug)]
pub struct UserEvent {
/// The originator of the event trigger
pub scope_id: ScopeId,
pub priority: EventPriority,
/// The optional real node associated with the trigger
pub mounted_dom_id: Option<ElementId>,
/// The event type IE "onclick" or "onmouseover"
///
/// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
/// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
/// exhaust the deadline working on them.
/// The name that the renderer will use to mount the listener.
pub name: &'static str,
/// Event Data
pub event: Box<dyn Any + Send>,
}
/// 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 RealDOM. 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.
///
/// 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.
/// 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.
///
/// Due to platform differences in how time is handled, this method accepts a future that resolves when the deadline
/// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
/// deadline closure manually.
/// This is typically reserved for things like user interaction.
///
/// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
/// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
/// the screen will "jank" up. In debug, this will trigger an alert.
/// 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.
///
/// If there are no in-flight fibers when this method is called, it will await any possible tasks, aborting early if
/// the provided deadline future resolves.
/// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input.
///
/// 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.
/// 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.
///
/// # Example
/// The primary user of Low Priority work is the asynchronous work system (Suspense).
///
/// ```no_run
/// static App: FC<()> = |(cx, props)|rsx!(cx, div {"hello"} );
/// let mut dom = VirtualDom::new(App);
/// loop {
/// let deadline = TimeoutFuture::from_ms(16);
/// let mutations = dom.run_with_deadline(deadline).await;
/// apply_mutations(mutations);
/// }
/// ```
///
/// ## 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 fn run_with_deadline(&mut self, deadline: impl FnMut() -> bool) -> Vec<Mutations<'_>> {
self.scheduler.work_with_deadline(deadline)
}
pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
self.scheduler.pool.channel.sender.clone()
}
/// Waits for the scheduler to have work
/// This lets us poll async tasks during idle periods without blocking the main thread.
pub async fn wait_for_work(&mut self) {
// todo: poll the events once even if there is work to do to prevent starvation
if self.scheduler.has_any_work() {
return;
}
use futures_util::StreamExt;
// Wait for any new events if we have nothing to do
let tasks_fut = self.scheduler.async_tasks.next();
let scheduler_fut = self.scheduler.receiver.next();
use futures_util::future::{select, Either};
match select(tasks_fut, scheduler_fut).await {
// poll the internal futures
Either::Left((_id, _)) => {
//
}
// wait for an external event
Either::Right((msg, _)) => match msg.unwrap() {
SchedulerMsg::Task(t) => {
self.scheduler.handle_task(t);
}
SchedulerMsg::Immediate(im) => {
self.scheduler.dirty_scopes.insert(im);
}
SchedulerMsg::UiEvent(evt) => {
self.scheduler.ui_events.push_back(evt);
}
},
}
}
/// This is considered "idle" work or "background" work.
Low = 0,
}
+4 -4
View File
@@ -10,8 +10,8 @@ fn test_borrowed_state() {
let _ = VirtualDom::new(Parent);
}
fn Parent((cx, _): Scope<()>) -> Element {
let value = cx.use_hook(|_| String::new(), |f| &*f, |_| {});
fn Parent(cx: Context, props: &()) -> Element {
let value = cx.use_hook(|_| String::new(), |f| &*f);
cx.render(rsx! {
div {
@@ -28,7 +28,7 @@ struct ChildProps<'a> {
name: &'a str,
}
fn Child<'a>((cx, props): Scope<'a, ChildProps>) -> Element<'a> {
fn Child(cx: Context, props: &ChildProps) -> Element {
cx.render(rsx! {
div {
h1 { "it's nested" }
@@ -42,7 +42,7 @@ struct Grandchild<'a> {
name: &'a str,
}
fn Child2<'a>((cx, props): Scope<'a, Grandchild>) -> Element<'a> {
fn Child2(cx: Context, props: &Grandchild) -> Element {
cx.render(rsx! {
div { "Hello {props.name}!" }
})
+22 -21
View File
@@ -21,7 +21,7 @@ fn new_dom<P: 'static + Send>(app: FC<P>, props: P) -> VirtualDom {
#[test]
fn test_original_diff() {
static APP: FC<()> = |(cx, props)| {
static APP: FC<()> = |cx, props| {
cx.render(rsx! {
div {
div {
@@ -57,17 +57,17 @@ fn test_original_diff() {
#[test]
fn create() {
static APP: FC<()> = |(cx, props)| {
static APP: FC<()> = |cx, props| {
cx.render(rsx! {
div {
div {
"Hello, world!"
div {
div {
// Fragment {
// "hello"
// "world"
// }
Fragment {
"hello"
"world"
}
}
}
}
@@ -120,7 +120,7 @@ fn create() {
#[test]
fn create_list() {
static APP: FC<()> = |(cx, props)| {
static APP: FC<()> = |cx, props| {
cx.render(rsx! {
{(0..3).map(|f| rsx!{ div {
"hello"
@@ -169,7 +169,7 @@ fn create_list() {
#[test]
fn create_simple() {
static APP: FC<()> = |(cx, props)| {
static APP: FC<()> = |cx, props| {
cx.render(rsx! {
div {}
div {}
@@ -207,7 +207,7 @@ fn create_simple() {
}
#[test]
fn create_components() {
static App: FC<()> = |(cx, props)| {
static App: FC<()> = |cx, props| {
cx.render(rsx! {
Child { "abc1" }
Child { "abc2" }
@@ -220,7 +220,7 @@ fn create_components() {
children: ScopeChildren<'a>,
}
fn Child<'a>((cx, props): Scope<'a, ChildProps<'a>>) -> Element {
fn Child<'a>(cx: Context<'a>, props: &ChildProps<'a>) -> Element {
cx.render(rsx! {
h1 {}
div { {&props.children} }
@@ -273,7 +273,7 @@ fn create_components() {
}
#[test]
fn anchors() {
static App: FC<()> = |(cx, props)| {
static App: FC<()> = |cx, props| {
cx.render(rsx! {
{true.then(|| rsx!{ div { "hello" } })}
{false.then(|| rsx!{ div { "goodbye" } })}
@@ -302,17 +302,18 @@ fn anchors() {
#[test]
fn suspended() {
static App: FC<()> = |(cx, props)| {
let val = use_suspense(cx, || async {}, |cx, p| todo!());
todo!()
// static App: FC<()> = |cx, props| {
// let val = use_suspense(cx, || async {}, |p| todo!());
cx.render(rsx! { {val} })
};
// cx.render(rsx! { {val} })
// };
let mut dom = new_dom(App, ());
let mutations = dom.rebuild();
// let mut dom = new_dom(App, ());
// let mutations = dom.rebuild();
assert_eq!(
mutations.edits,
[CreatePlaceholder { root: 0 }, AppendChildren { many: 1 },]
);
// assert_eq!(
// mutations.edits,
// [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 },]
// );
}
+1 -1
View File
@@ -5,7 +5,7 @@
//!
//! It does not validated that component lifecycles work properly. This is done in another test file.
use dioxus::{nodes::VSuspended, prelude::*, DomEdit, TestDom};
use dioxus::{prelude::*, DomEdit, TestDom, VSuspended};
use dioxus_core as dioxus;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
+2 -4
View File
@@ -13,7 +13,7 @@ mod test_logging;
#[test]
fn please_work() {
static App: FC<()> = |(cx, props)| {
static App: FC<()> = |cx, props| {
cx.render(rsx! {
div {
hidden: "true"
@@ -27,7 +27,7 @@ fn please_work() {
})
};
static Child: FC<()> = |(cx, props)| {
static Child: FC<()> = |cx, props| {
cx.render(rsx! {
div { "child" }
})
@@ -35,6 +35,4 @@ fn please_work() {
let mut dom = VirtualDom::new(App);
dom.rebuild();
println!("{}", dom);
}
+2 -2
View File
@@ -20,7 +20,7 @@ fn manual_diffing() {
value: Shared<&'static str>,
}
static App: FC<AppProps> = |(cx, props)| {
static App: FC<AppProps> = |cx, props| {
let val = props.value.lock().unwrap();
cx.render(rsx! { div { "{val}" } })
};
@@ -37,7 +37,7 @@ fn manual_diffing() {
*value.lock().unwrap() = "goodbye";
let edits = dom.diff();
let edits = dom.rebuild();
log::debug!("edits: {:?}", edits);
}
+2 -2
View File
@@ -13,12 +13,12 @@ mod test_logging;
fn shared_state_test() {
struct MySharedState(&'static str);
static App: FC<()> = |(cx, props)| {
static App: FC<()> = |cx, props| {
cx.provide_state(MySharedState("world!"));
cx.render(rsx!(Child {}))
};
static Child: FC<()> = |(cx, props)| {
static Child: FC<()> = |cx, props| {
let shared = cx.consume_state::<MySharedState>()?;
cx.render(rsx!("Hello, {shared.0}"))
};
+15 -14
View File
@@ -17,7 +17,7 @@ use dioxus_html as dioxus_elements;
#[test]
fn app_runs() {
static App: FC<()> = |(cx, props)| {
static App: FC<()> = |cx, props| {
//
cx.render(rsx!( div{"hello"} ))
};
@@ -28,7 +28,7 @@ fn app_runs() {
#[test]
fn fragments_work() {
static App: FC<()> = |(cx, props)| {
static App: FC<()> = |cx, props| {
cx.render(rsx!(
div{"hello"}
div{"goodbye"}
@@ -42,7 +42,7 @@ fn fragments_work() {
#[test]
fn lists_work() {
static App: FC<()> = |(cx, props)| {
static App: FC<()> = |cx, props| {
cx.render(rsx!(
h1 {"hello"}
{(0..6).map(|f| rsx!(span{ "{f}" }))}
@@ -55,7 +55,7 @@ fn lists_work() {
#[test]
fn conditional_rendering() {
static App: FC<()> = |(cx, props)| {
static App: FC<()> = |cx, props| {
cx.render(rsx!(
h1 {"hello"}
{true.then(|| rsx!(span{ "a" }))}
@@ -72,13 +72,13 @@ fn conditional_rendering() {
#[test]
fn child_components() {
static App: FC<()> = |(cx, props)| {
static App: FC<()> = |cx, props| {
cx.render(rsx!(
{true.then(|| rsx!(Child { }))}
{false.then(|| rsx!(Child { }))}
))
};
static Child: FC<()> = |(cx, props)| {
static Child: FC<()> = |cx, props| {
cx.render(rsx!(
h1 {"hello"}
h1 {"goodbye"}
@@ -91,13 +91,14 @@ fn child_components() {
#[test]
fn suspended_works() {
static App: FC<()> = |(cx, props)| {
let title = use_suspense(cx, || async { "bob" }, move |cx, f| todo!());
// let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"});
cx.render(rsx!("hello" { title }))
};
todo!()
// static App: FC<()> = |cx, props| {
// let title = use_suspense(cx, || async { "bob" }, move |cx, f| todo!());
// // let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"});
// cx.render(rsx!("hello" { title }))
// };
let mut vdom = VirtualDom::new(App);
let edits = vdom.rebuild();
dbg!(edits);
// let mut vdom = VirtualDom::new(App);
// let edits = vdom.rebuild();
// dbg!(edits);
}
+1 -1
View File
@@ -24,6 +24,7 @@ tokio = { version = "1.12.0", features = [
"rt",
], optional = true, default-features = false }
dioxus-core-macro = { path = "../core-macro" }
dioxus-html = { path = "../html", features = ["serialize"] }
[features]
default = ["tokio_runtime"]
@@ -31,6 +32,5 @@ tokio_runtime = ["tokio"]
[dev-dependencies]
dioxus-html = { path = "../html" }
dioxus-hooks = { path = "../hooks" }
simple_logger = "1.13.0"
+1 -4
View File
@@ -57,7 +57,7 @@ pub struct WebviewWindowProps<'a> {
///
///
///
pub fn WebviewWindow<'a>((cx, props): Scope<'a, WebviewWindowProps>) -> Element<'a> {
pub fn WebviewWindow((cx, props): Scope<WebviewWindowProps>) -> Element {
let dtcx = cx.consume_state::<RefCell<DesktopContext>>()?;
cx.use_hook(
@@ -67,9 +67,6 @@ pub fn WebviewWindow<'a>((cx, props): Scope<'a, WebviewWindowProps>) -> Element<
|state| {
//
},
|hook| {
//
},
);
// render the children directly
+1
View File
@@ -0,0 +1 @@
+6 -6
View File
@@ -4,7 +4,8 @@
use std::sync::Arc;
use std::{any::Any, rc::Rc};
use dioxus_core::{events::on::MouseEvent, ElementId, EventPriority, ScopeId, UserEvent};
use dioxus_core::{ElementId, EventPriority, ScopeId, UserEvent};
use dioxus_html::on::*;
#[derive(serde::Serialize, serde::Deserialize)]
struct ImEvent {
@@ -23,7 +24,7 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
contents,
} = ims.into_iter().next().unwrap();
let scope = ScopeId(scope as usize);
let scope_id = ScopeId(scope as usize);
let mounted_dom_id = Some(ElementId(mounted_dom_id as usize));
let name = event_name_from_typ(&event);
@@ -31,15 +32,14 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
UserEvent {
name,
event,
scope,
priority: EventPriority::Low,
scope_id,
mounted_dom_id,
event,
}
}
fn make_synthetic_event(name: &str, val: serde_json::Value) -> Box<dyn Any + Send> {
use dioxus_core::events::on::*;
match name {
"copy" | "cut" | "paste" => {
//
-1
View File
@@ -13,7 +13,6 @@ use std::sync::mpsc::channel;
use std::sync::{Arc, RwLock};
use cfg::DesktopConfig;
use dioxus_core::scheduler::SchedulerMsg;
use dioxus_core::*;
use serde::{Deserialize, Serialize};
+9 -8
View File
@@ -82,13 +82,6 @@ pub fn use_shared_state<'a, T: 'static>(cx: Context<'a>) -> Option<UseSharedStat
_ => None,
}
},
|f| {
// we need to unsubscribe when our component is unounted
if let Some(root) = &f.root {
let mut root = root.borrow_mut();
root.consumers.remove(&f.scope_id);
}
},
)
}
@@ -98,6 +91,15 @@ struct SharedStateInner<T: 'static> {
scope_id: ScopeId,
needs_notification: Cell<bool>,
}
impl<T> Drop for SharedStateInner<T> {
fn drop(&mut self) {
// we need to unsubscribe when our component is unounted
if let Some(root) = &self.root {
let mut root = root.borrow_mut();
root.consumers.remove(&self.scope_id);
}
}
}
pub struct UseSharedState<'a, T: 'static> {
pub(crate) cx: Context<'a>,
@@ -172,6 +174,5 @@ pub fn use_provide_state<'a, T: 'static>(cx: Context<'a>, f: impl FnOnce() -> T)
cx.provide_state(state)
},
|inner| {},
|_| {},
)
}
-1
View File
@@ -16,7 +16,6 @@ pub fn use_ref<T: 'static>(cx: Context, f: impl FnOnce() -> T) -> UseRef<T> {
inner.update_scheuled.set(false);
UseRef { inner }
},
|_| {},
)
}
-1
View File
@@ -69,7 +69,6 @@ pub fn use_state<'a, T: 'static>(
UseState { inner: &*hook }
},
|_| {},
)
}
struct UseStateInner<T: 'static> {
+8 -3
View File
@@ -9,7 +9,12 @@ description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Vir
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
dioxus-core = { path = "../core", version = "0.1.2" }
dioxus-core = { path = "../core", version = "0.1.3" }
# Serialize the Edits for use in Webview/Liveview instances
serde = { version = "1", features = ["derive"], optional = true }
serde_repr = { version = "0.1.7", optional = true }
[dev-dependencies]
scraper = "0.12.0"
[features]
default = []
serialize = ["serde", "serde_repr"]
-33
View File
@@ -1,33 +0,0 @@
//! This module is not included anywhere.
//!
//! It is a prototype for a system that supports non-string attribute values.
trait AsAttributeValue: Sized {
fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a>;
}
enum AttributeValue<'a> {
Int(i32),
Float(f32),
Str(&'a str),
Bool(bool),
}
impl<'b> AsAttributeValue for Arguments<'b> {
fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
todo!()
}
}
impl AsAttributeValue for &'static str {
fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
todo!()
}
}
impl AsAttributeValue for f32 {
fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
todo!()
}
}
impl AsAttributeValue for i32 {
fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> {
todo!()
}
}
File diff suppressed because it is too large. Load diff
@@ -1,89 +1,7 @@
//! An event system that's less confusing than Traits + RC;
//! This should hopefully make it easier to port to other platforms.
//!
//! Unfortunately, it is less efficient than the original, but hopefully it's negligible.
use crate::{
innerlude::Listener,
innerlude::{ElementId, NodeFactory, ScopeId},
};
use bumpalo::boxed::Box as BumpBox;
use std::{
any::Any,
cell::{Cell, RefCell},
fmt::Debug,
};
pub use on::*;
#[derive(Debug)]
pub struct UserEvent {
/// The originator of the event trigger
pub scope: ScopeId,
/// The optional real node associated with the trigger
pub mounted_dom_id: Option<ElementId>,
/// The event type IE "onclick" or "onmouseover"
///
/// The name that the renderer will use to mount the listener.
pub name: &'static str,
/// The type of event
pub event: Box<dyn Any + Send>,
}
/// 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 RealDOM. 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,
}
use dioxus_core::exports::bumpalo;
use dioxus_core::*;
use std::any::Any;
pub mod on {
use super::*;
@@ -125,11 +43,7 @@ pub mod on {
// ie copy
let shortname: &'static str = &event_name[2..];
Listener {
event: shortname,
mounted_node: Cell::new(None),
callback: RefCell::new(Some(callback)),
}
c.listener(shortname, callback)
}
)*
)*
@@ -1148,7 +1062,7 @@ impl KeyCode {
}
}
pub(crate) fn event_meta(event: &UserEvent) -> (bool, EventPriority) {
pub(crate) fn _event_meta(event: &UserEvent) -> (bool, EventPriority) {
use EventPriority::*;
match event.name {
+925
View File
@@ -0,0 +1,925 @@
use dioxus_core::*;
use std::fmt::Arguments;
macro_rules! no_namespace_trait_methods {
(
$(
$(#[$attr:meta])*
$name:ident;
)*
) => {
$(
$(#[$attr])*
fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr(stringify!($name), val, None, false)
}
)*
};
}
macro_rules! style_trait_methods {
(
$(
$(#[$attr:meta])*
$name:ident: $lit:literal,
)*
) => {
$(
#[inline]
$(#[$attr])*
fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr($lit, val, Some("style"), false)
}
)*
};
}
macro_rules! aria_trait_methods {
(
$(
$(#[$attr:meta])*
$name:ident: $lit:literal,
)*
) => {
$(
$(#[$attr])*
fn $name<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> {
cx.attr($lit, val, None, false)
}
)*
};
}
pub trait GlobalAttributes {
no_namespace_trait_methods! {
accesskey;
/// The HTML class attribute is used to specify a class for an HTML element.
///
/// ## Details
/// Multiple HTML elements can share the same class.
///
/// The class global attribute is a space-separated list of the case-sensitive classes of the element.
/// Classes allow CSS and Javascript to select and access specific elements via the class selectors or
/// functions like the DOM method document.getElementsByClassName.
///
/// ## Example
///
/// ### HTML:
/// ```html
/// <p class="note editorial">Above point sounds a bit obvious. Remove/rewrite?</p>
/// ```
///
/// ### CSS:
/// ```css
/// .note {
/// font-style: italic;
/// font-weight: bold;
/// }
///
/// .editorial {
/// background: rgb(255, 0, 0, .25);
/// padding: 10px;
/// }
/// ```
class;
contenteditable;
data;
dir;
draggable;
hidden;
id;
lang;
spellcheck;
style;
tabindex;
title;
translate;
/// dangerous_inner_html is Dioxus's replacement for using innerHTML in the browser DOM. In general, setting
/// HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS)
/// attack. So, you can set HTML directly from Dioxus, but you have to type out dangerous_inner_html to remind
/// yourself that it’s dangerous
dangerous_inner_html;
}
// This macro creates an explicit method call for each of the style attributes.
//
// The left token specifies the name of the attribute in the rsx! macro, and the right string literal specifies the
// actual name of the attribute generated.
//
// This roughly follows the html spec
style_trait_methods! {
/// Specifies the alignment of flexible container's items within the flex container.
align_content: "align-content",
/// Specifies the default alignment for items within the flex container.
align_items: "align-items",
/// Specifies the alignment for selected items within the flex container.
align_self: "align-self",
/// Specifies the keyframe_based animations.
animation: "animation",
/// Specifies when the animation will start.
animation_delay: "animation-delay",
/// Specifies whether the animation should play in reverse on alternate cycles or not.
animation_direction: "animation-direction",
/// Specifies the number of seconds or milliseconds an animation should take to complete one cycle
animation_duration: "animation-duration",
/// Specifies how a CSS animation should apply styles to its target before and after it is executing
animation_fill_mode: "animation-fill-mode",
/// Specifies the number of times an animation cycle should be played before stopping.
animation_iteration_count: "animation-iteration-count",
/// Specifies the name of @keyframes defined animations that should be applied to the selected element
animation_name: "animation-name",
/// Specifies whether the animation is running or paused.
animation_play_state: "animation-play-state",
/// Specifies how a CSS animation should progress over the duration of each cycle.
animation_timing_function: "animation-timing-function",
/// Specifies whether or not the "back" side of a transformed element is visible when facing the user.
backface_visibility: "backface-visibility",
/// Defines a variety of background properties within one declaration.
background: "background",
/// Specify whether the background image is fixed in the viewport or scrolls.
background_attachment: "background-attachment",
/// Specifies the painting area of the background.
background_clip: "background-clip",
/// Defines an element's background color.
background_color: "background-color",
/// Defines an element's background image.
background_image: "background-image",
/// Specifies the positioning area of the background images.
background_origin: "background-origin",
/// Defines the origin of a background image.
background_position: "background-position",
/// Specify whether/how the background image is tiled.
background_repeat: "background-repeat",
/// Specifies the size of the background images.
background_size: "background-size",
/// Sets the width, style, and color for all four sides of an element's border.
border: "border",
/// Sets the width, style, and color of the bottom border of an element.
border_bottom: "border-bottom",
/// Sets the color of the bottom border of an element.
border_bottom_color: "border-bottom-color",
/// Defines the shape of the bottom_left border corner of an element.
border_bottom_left_radius: "border-bottom-left-radius",
/// Defines the shape of the bottom_right border corner of an element.
border_bottom_right_radius: "border-bottom-right-radius",
/// Sets the style of the bottom border of an element.
border_bottom_style: "border-bottom-style",
/// Sets the width of the bottom border of an element.
border_bottom_width: "border-bottom-width",
/// Specifies whether table cell borders are connected or separated.
border_collapse: "border-collapse",
/// Sets the color of the border on all the four sides of an element.
border_color: "border-color",
/// Specifies how an image is to be used in place of the border styles.
border_image: "border-image",
/// Specifies the amount by which the border image area extends beyond the border box.
border_image_outset: "border-image-outset",
/// Specifies whether the image_border should be repeated, rounded or stretched.
border_image_repeat: "border-image-repeat",
/// Specifies the inward offsets of the image_border.
border_image_slice: "border-image-slice",
/// Specifies the location of the image to be used as a border.
border_image_source: "border-image-source",
/// Specifies the width of the image_border.
border_image_width: "border-image-width",
/// Sets the width, style, and color of the left border of an element.
border_left: "border-left",
/// Sets the color of the left border of an element.
border_left_color: "border-left-color",
/// Sets the style of the left border of an element.
border_left_style: "border-left-style",
/// Sets the width of the left border of an element.
border_left_width: "border-left-width",
/// Defines the shape of the border corners of an element.
border_radius: "border-radius",
/// Sets the width, style, and color of the right border of an element.
border_right: "border-right",
/// Sets the color of the right border of an element.
border_right_color: "border-right-color",
/// Sets the style of the right border of an element.
border_right_style: "border-right-style",
/// Sets the width of the right border of an element.
border_right_width: "border-right-width",
/// Sets the spacing between the borders of adjacent table cells.
border_spacing: "border-spacing",
/// Sets the style of the border on all the four sides of an element.
border_style: "border-style",
/// Sets the width, style, and color of the top border of an element.
border_top: "border-top",
/// Sets the color of the top border of an element.
border_top_color: "border-top-color",
/// Defines the shape of the top_left border corner of an element.
border_top_left_radius: "border-top-left-radius",
/// Defines the shape of the top_right border corner of an element.
border_top_right_radius: "border-top-right-radius",
/// Sets the style of the top border of an element.
border_top_style: "border-top-style",
/// Sets the width of the top border of an element.
border_top_width: "border-top-width",
/// Sets the width of the border on all the four sides of an element.
border_width: "border-width",
/// Specify the location of the bottom edge of the positioned element.
bottom: "bottom",
/// Applies one or more drop_shadows to the element's box.
box_shadow: "box-shadow",
/// Alter the default CSS box model.
box_sizing: "box-sizing",
/// Specify the position of table's caption.
caption_side: "caption-side",
/// Specifies the placement of an element in relation to floating elements.
clear: "clear",
/// Defines the clipping region.
clip: "clip",
/// Specify the color of the text of an element.
color: "color",
/// Specifies the number of columns in a multi_column element.
column_count: "column-count",
/// Specifies how columns will be filled.
column_fill: "column-fill",
/// Specifies the gap between the columns in a multi_column element.
column_gap: "column-gap",
/// Specifies a straight line, or "rule", to be drawn between each column in a multi_column element.
column_rule: "column-rule",
/// Specifies the color of the rules drawn between columns in a multi_column layout.
column_rule_color: "column-rule-color",
/// Specifies the style of the rule drawn between the columns in a multi_column layout.
column_rule_style: "column-rule-style",
/// Specifies the width of the rule drawn between the columns in a multi_column layout.
column_rule_width: "column-rule-width",
/// Specifies how many columns an element spans across in a multi_column layout.
column_span: "column-span",
/// Specifies the optimal width of the columns in a multi_column element.
column_width: "column-width",
/// A shorthand property for setting column_width and column_count properties.
columns: "columns",
/// Inserts generated content.
content: "content",
/// Increments one or more counter values.
counter_increment: "counter-increment",
/// Creates or resets one or more counters.
counter_reset: "counter-reset",
/// Specify the type of cursor.
cursor: "cursor",
/// Define the text direction/writing direction.
direction: "direction",
/// Specifies how an element is displayed onscreen.
display: "display",
/// Show or hide borders and backgrounds of empty table cells.
empty_cells: "empty-cells",
/// Specifies the components of a flexible length.
flex: "flex",
/// Specifies the initial main size of the flex item.
flex_basis: "flex-basis",
/// Specifies the direction of the flexible items.
flex_direction: "flex-direction",
/// A shorthand property for the flex_direction and the flex_wrap properties.
flex_flow: "flex-flow",
/// Specifies how the flex item will grow relative to the other items inside the flex container.
flex_grow: "flex-grow",
/// Specifies how the flex item will shrink relative to the other items inside the flex container
flex_shrink: "flex-shrink",
/// Specifies whether the flexible items should wrap or not.
flex_wrap: "flex-wrap",
/// Specifies whether or not a box should float.
float: "float",
/// Defines a variety of font properties within one declaration.
font: "font",
/// Defines a list of fonts for element.
font_family: "font-family",
/// Defines the font size for the text.
font_size: "font-size",
/// Preserves the readability of text when font fallback occurs.
font_size_adjust: "font-size-adjust",
/// Selects a normal, condensed, or expanded face from a font.
font_stretch: "font-stretch",
/// Defines the font style for the text.
font_style: "font-style",
/// Specify the font variant.
font_variant: "font-variant",
/// Specify the font weight of the text.
font_weight: "font-weight",
/// Specify the height of an element.
height: "height",
/// Specifies how flex items are aligned along the main axis of the flex container after any flexible lengths and auto margins have been resolved.
justify_content: "auto margins have been resolved.",
/// Specify the location of the left edge of the positioned element.
left: "left",
/// Sets the extra spacing between letters.
letter_spacing: "letter-spacing",
/// Sets the height between lines of text.
line_height: "line-height",
/// Defines the display style for a list and list elements.
list_style: "list-style",
/// Specifies the image to be used as a list_item marker.
list_style_image: "list-style-image",
/// Specifies the position of the list_item marker.
list_style_position: "list-style-position",
/// Specifies the marker style for a list_item.
list_styler_type: "list-style-type",
/// Sets the margin on all four sides of the element.
margin: "margin",
/// Sets the bottom margin of the element.
margin_bottom: "margin-bottom",
/// Sets the left margin of the element.
margin_left: "margin-left",
/// Sets the right margin of the element.
margin_right: "margin-right",
/// Sets the top margin of the element.
margin_top: "margin-top",
/// Specify the maximum height of an element.
max_height: "max-height",
/// Specify the maximum width of an element.
max_width: "max-width",
/// Specify the minimum height of an element.
min_height: "min-height",
/// Specify the minimum width of an element.
min_width: "min-width",
/// Specifies the transparency of an element.
opacity: "opacity",
/// Specifies the order in which a flex items are displayed and laid out within a flex container.
order: "order",
/// Sets the width, style, and color for all four sides of an element's outline.
outline: "outline",
/// Sets the color of the outline.
outline_color: "outline-color",
/// Set the space between an outline and the border edge of an element.
outline_offset: "outline-offset",
/// Sets a style for an outline.
outline_style: "outline-style",
/// Sets the width of the outline.
outline_width: "outline-width",
/// Specifies the treatment of content that overflows the element's box.
overflow: "overflow",
/// Specifies the treatment of content that overflows the element's box horizontally.
overflow_x: "overflow-x",
/// Specifies the treatment of content that overflows the element's box vertically.
overflow_y: "overflow-y",
/// Sets the padding on all four sides of the element.
padding: "padding",
/// Sets the padding to the bottom side of an element.
padding_bottom: "padding-bottom",
/// Sets the padding to the left side of an element.
padding_left: "padding-left",
/// Sets the padding to the right side of an element.
padding_right: "padding-right",
/// Sets the padding to the top side of an element.
padding_top: "padding-top",
/// Insert a page breaks after an element.
page_break_after: "page-break-after",
/// Insert a page breaks before an element.
page_break_before: "page-break-before",
/// Insert a page breaks inside an element.
page_break_inside: "page-break-inside",
/// Defines the perspective from which all child elements of the object are viewed.
perspective: "perspective",
/// Defines the origin (the vanishing point for the 3D space) for the perspective property.
perspective_origin: "perspective-origin",
/// Specifies how an element is positioned.
position: "position",
/// The pointer-events CSS property sets under what circumstances (if any) a particular graphic element can
/// become the target of pointer events.
///
/// MDN: [`pointer_events`](https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events)
pointer_events: "pointer-events",
/// Specifies quotation marks for embedded quotations.
quotes: "quotes",
/// Specifies whether or not an element is resizable by the user.
resize: "resize",
/// Specify the location of the right edge of the positioned element.
right: "right",
/// Specifies the length of the tab character.
tab_size: "tab-size",
/// Specifies a table layout algorithm.
table_layout: "table-layout",
/// Sets the horizontal alignment of inline content.
text_align: "text-align",
/// Specifies how the last line of a block or a line right before a forced line break is aligned when is justify.",
text_align_last: "text-align-last",
/// Specifies the decoration added to text.
text_decoration: "text-decoration",
/// Specifies the color of the text_decoration_line.
text_decoration_color: "text-decoration-color",
/// Specifies what kind of line decorations are added to the element.
text_decoration_line: "text-decoration-line",
/// Specifies the style of the lines specified by the text_decoration_line property
text_decoration_style: "text-decoration-style",
/// Indent the first line of text.
text_indent: "text-indent",
/// Specifies the justification method to use when the text_align property is set to justify.
text_justify: "text-justify",
/// Specifies how the text content will be displayed, when it overflows the block containers.
text_overflow: "text-overflow",
/// Applies one or more shadows to the text content of an element.
text_shadow: "text-shadow",
/// Transforms the case of the text.
text_transform: "text-transform",
/// Specify the location of the top edge of the positioned element.
top: "top",
/// Applies a 2D or 3D transformation to an element.
transform: "transform",
/// Defines the origin of transformation for an element.
transform_origin: "transform-origin",
/// Specifies how nested elements are rendered in 3D space.
transform_style: "transform-style",
/// Defines the transition between two states of an element.
transition: "transition",
/// Specifies when the transition effect will start.
transition_delay: "transition-delay",
/// Specifies the number of seconds or milliseconds a transition effect should take to complete.
transition_duration: "transition-duration",
/// Specifies the names of the CSS properties to which a transition effect should be applied.
transition_property: "transition-property",
/// Specifies the speed curve of the transition effect.
transition_timing_function: "transition-timing-function",
/// Sets the vertical positioning of an element relative to the current text baseline.
vertical_align: "vertical-align",
/// Specifies whether or not an element is visible.
visibility: "visibility",
/// Specifies how white space inside the element is handled.
white_space: "white-space",
/// Specify the width of an element.
width: "width",
/// Specifies how to break lines within words.
word_break: "word-break",
/// Sets the spacing between words.
word_spacing: "word-spacing",
/// Specifies whether to break words when the content overflows the boundaries of its container.
word_wrap: "word-wrap",
/// Specifies a layering or stacking order for positioned elements.
z_index : "z-index ",
}
aria_trait_methods! {
aria_current: "aria-current",
aria_details: "aria-details",
aria_disabled: "aria-disabled",
aria_hidden: "aria-hidden",
aria_invalid: "aria-invalid",
aria_keyshortcuts: "aria-keyshortcuts",
aria_label: "aria-label",
aria_roledescription: "aria-roledescription",
// Widget Attributes
aria_autocomplete: "aria-autocomplete",
aria_checked: "aria-checked",
aria_expanded: "aria-expanded",
aria_haspopup: "aria-haspopup",
aria_level: "aria-level",
aria_modal: "aria-modal",
aria_multiline: "aria-multiline",
aria_multiselectable: "aria-multiselectable",
aria_orientation: "aria-orientation",
aria_placeholder: "aria-placeholder",
aria_pressed: "aria-pressed",
aria_readonly: "aria-readonly",
aria_required: "aria-required",
aria_selected: "aria-selected",
aria_sort: "aria-sort",
aria_valuemax: "aria-valuemax",
aria_valuemin: "aria-valuemin",
aria_valuenow: "aria-valuenow",
aria_valuetext: "aria-valuetext",
// Live Region Attributes
aria_atomic: "aria-atomic",
aria_busy: "aria-busy",
aria_live: "aria-live",
aria_relevant: "aria-relevant",
aria_dropeffect: "aria-dropeffect",
aria_grabbed: "aria-grabbed",
// Relationship Attributes
aria_activedescendant: "aria-activedescendant",
aria_colcount: "aria-colcount",
aria_colindex: "aria-colindex",
aria_colspan: "aria-colspan",
aria_controls: "aria-controls",
aria_describedby: "aria-describedby",
aria_errormessage: "aria-errormessage",
aria_flowto: "aria-flowto",
aria_labelledby: "aria-labelledby",
aria_owns: "aria-owns",
aria_posinset: "aria-posinset",
aria_rowcount: "aria-rowcount",
aria_rowindex: "aria-rowindex",
aria_rowspan: "aria-rowspan",
aria_setsize: "aria-setsize",
}
}
pub trait SvgAttributes {
aria_trait_methods! {
accent_height: "accent-height",
accumulate: "accumulate",
additive: "additive",
alignment_baseline: "alignment-baseline",
alphabetic: "alphabetic",
amplitude: "amplitude",
arabic_form: "arabic-form",
ascent: "ascent",
attributeName: "attributeName",
attributeType: "attributeType",
azimuth: "azimuth",
baseFrequency: "baseFrequency",
baseline_shift: "baseline-shift",
baseProfile: "baseProfile",
bbox: "bbox",
begin: "begin",
bias: "bias",
by: "by",
calcMode: "calcMode",
cap_height: "cap-height",
class: "class",
clip: "clip",
clipPathUnits: "clipPathUnits",
clip_path: "clip-path",
clip_rule: "clip-rule",
color: "color",
color_interpolation: "color-interpolation",
color_interpolation_filters: "color-interpolation-filters",
color_profile: "color-profile",
color_rendering: "color-rendering",
contentScriptType: "contentScriptType",
contentStyleType: "contentStyleType",
crossorigin: "crossorigin",
cursor: "cursor",
cx: "cx",
cy: "cy",
d: "d",
decelerate: "decelerate",
descent: "descent",
diffuseConstant: "diffuseConstant",
direction: "direction",
display: "display",
divisor: "divisor",
dominant_baseline: "dominant-baseline",
dur: "dur",
dx: "dx",
dy: "dy",
edgeMode: "edgeMode",
elevation: "elevation",
enable_background: "enable-background",
end: "end",
exponent: "exponent",
fill: "fill",
fill_opacity: "fill-opacity",
fill_rule: "fill-rule",
filter: "filter",
filterRes: "filterRes",
filterUnits: "filterUnits",
flood_color: "flood-color",
flood_opacity: "flood-opacity",
font_family: "font-family",
font_size: "font-size",
font_size_adjust: "font-size-adjust",
font_stretch: "font-stretch",
font_style: "font-style",
font_variant: "font-variant",
font_weight: "font-weight",
format: "format",
from: "from",
fr: "fr",
fx: "fx",
fy: "fy",
g1: "g1",
g2: "g2",
glyph_name: "glyph-name",
glyph_orientation_horizontal: "glyph-orientation-horizontal",
glyph_orientation_vertical: "glyph-orientation-vertical",
glyphRef: "glyphRef",
gradientTransform: "gradientTransform",
gradientUnits: "gradientUnits",
hanging: "hanging",
height: "height",
href: "href",
hreflang: "hreflang",
horiz_adv_x: "horiz-adv-x",
horiz_origin_x: "horiz-origin-x",
id: "id",
ideographic: "ideographic",
image_rendering: "image-rendering",
_in: "_in",
in2: "in2",
intercept: "intercept",
k: "k",
k1: "k1",
k2: "k2",
k3: "k3",
k4: "k4",
kernelMatrix: "kernelMatrix",
kernelUnitLength: "kernelUnitLength",
kerning: "kerning",
keyPoints: "keyPoints",
keySplines: "keySplines",
keyTimes: "keyTimes",
lang: "lang",
lengthAdjust: "lengthAdjust",
letter_spacing: "letter-spacing",
lighting_color: "lighting-color",
limitingConeAngle: "limitingConeAngle",
local: "local",
marker_end: "marker-end",
marker_mid: "marker-mid",
marker_start: "marker_start",
markerHeight: "markerHeight",
markerUnits: "markerUnits",
markerWidth: "markerWidth",
mask: "mask",
maskContentUnits: "maskContentUnits",
maskUnits: "maskUnits",
mathematical: "mathematical",
max: "max",
media: "media",
method: "method",
min: "min",
mode: "mode",
name: "name",
numOctaves: "numOctaves",
offset: "offset",
opacity: "opacity",
operator: "operator",
order: "order",
orient: "orient",
orientation: "orientation",
origin: "origin",
overflow: "overflow",
overline_position: "overline-position",
overline_thickness: "overline-thickness",
panose_1: "panose-1",
paint_order: "paint-order",
path: "path",
pathLength: "pathLength",
patternContentUnits: "patternContentUnits",
patternTransform: "patternTransform",
patternUnits: "patternUnits",
ping: "ping",
pointer_events: "pointer-events",
points: "points",
pointsAtX: "pointsAtX",
pointsAtY: "pointsAtY",
pointsAtZ: "pointsAtZ",
preserveAlpha: "preserveAlpha",
preserveAspectRatio: "preserveAspectRatio",
primitiveUnits: "primitiveUnits",
r: "r",
radius: "radius",
referrerPolicy: "referrerPolicy",
refX: "refX",
refY: "refY",
rel: "rel",
rendering_intent: "rendering-intent",
repeatCount: "repeatCount",
repeatDur: "repeatDur",
requiredExtensions: "requiredExtensions",
requiredFeatures: "requiredFeatures",
restart: "restart",
result: "result",
rotate: "rotate",
rx: "rx",
ry: "ry",
scale: "scale",
seed: "seed",
shape_rendering: "shape-rendering",
slope: "slope",
spacing: "spacing",
specularConstant: "specularConstant",
specularExponent: "specularExponent",
speed: "speed",
spreadMethod: "spreadMethod",
startOffset: "startOffset",
stdDeviation: "stdDeviation",
stemh: "stemh",
stemv: "stemv",
stitchTiles: "stitchTiles",
stop_color: "stop_color",
stop_opacity: "stop_opacity",
strikethrough_position: "strikethrough-position",
strikethrough_thickness: "strikethrough-thickness",
string: "string",
stroke: "stroke",
stroke_dasharray: "stroke-dasharray",
stroke_dashoffset: "stroke-dashoffset",
stroke_linecap: "stroke-linecap",
stroke_linejoin: "stroke-linejoin",
stroke_miterlimit: "stroke-miterlimit",
stroke_opacity: "stroke-opacity",
stroke_width: "stroke-width",
style: "style",
surfaceScale: "surfaceScale",
systemLanguage: "systemLanguage",
tabindex: "tabindex",
tableValues: "tableValues",
target: "target",
targetX: "targetX",
targetY: "targetY",
text_anchor: "text-anchor",
text_decoration: "text-decoration",
text_rendering: "text-rendering",
textLength: "textLength",
to: "to",
transform: "transform",
transform_origin: "transform-origin",
r#type: "_type",
u1: "u1",
u2: "u2",
underline_position: "underline-position",
underline_thickness: "underline-thickness",
unicode: "unicode",
unicode_bidi: "unicode-bidi",
unicode_range: "unicode-range",
units_per_em: "units-per-em",
v_alphabetic: "v-alphabetic",
v_hanging: "v-hanging",
v_ideographic: "v-ideographic",
v_mathematical: "v-mathematical",
values: "values",
vector_effect: "vector-effect",
version: "version",
vert_adv_y: "vert-adv-y",
vert_origin_x: "vert-origin-x",
vert_origin_y: "vert-origin-y",
view_box: "viewBox",
view_target: "viewTarget",
visibility: "visibility",
width: "width",
widths: "widths",
word_spacing: "word-spacing",
writing_mode: "writing-mode",
x: "x",
x_height: "x-height",
x1: "x1",
x2: "x2",
xmlns: "xmlns",
x_channel_selector: "xChannelSelector",
y: "y",
y1: "y1",
y2: "y2",
y_channel_selector: "yChannelSelector",
z: "z",
zoomAndPan: "zoomAndPan",
}
}
+7 -2183
View File
File diff suppressed because it is too large. Load diff
+5 -8
View File
@@ -170,8 +170,11 @@
#[cfg(feature = "core")]
pub use dioxus_core as core;
#[cfg(feature = "core")]
pub use dioxus_core::events;
#[cfg(feature = "hooks")]
pub use dioxus_hooks as hooks;
#[cfg(feature = "ssr")]
pub use dioxus_ssr as ssr;
#[cfg(feature = "web")]
pub use dioxus_web as web;
@@ -179,12 +182,6 @@ pub use dioxus_web as web;
#[cfg(feature = "mobile")]
pub use dioxus_mobile as mobile;
#[cfg(feature = "ssr")]
pub use dioxus_ssr as ssr;
#[cfg(feature = "hooks")]
pub use dioxus_hooks as hooks;
#[cfg(feature = "desktop")]
pub use dioxus_desktop as desktop;