Merge pull request #32 from DioxusLabs/jk/reworking_components
wip: rework components, safety, and tasks
This commit is contained in:
58 files changed
+4191
-3813
No files matched your search
@@ -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
@@ -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}
|
||||
}
|
||||
))
|
||||
};
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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]]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
use dioxus_core as dioxus;
|
||||
use dioxus_core_macro::*;
|
||||
|
||||
fn main() {}
|
||||
|
||||
#[derive(Props)]
|
||||
struct ChildProps<'a> {
|
||||
name: &'a str,
|
||||
}
|
||||
@@ -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}!" }
|
||||
})
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)?;
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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,
|
||||
}
|
||||
@@ -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}!" }
|
||||
})
|
||||
|
||||
@@ -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 },]
|
||||
// );
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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}"))
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -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" => {
|
||||
//
|
||||
|
||||
@@ -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};
|
||||
|
||||
|
||||
@@ -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| {},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
@@ -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 }
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -69,7 +69,6 @@ pub fn use_state<'a, T: 'static>(
|
||||
|
||||
UseState { inner: &*hook }
|
||||
},
|
||||
|_| {},
|
||||
)
|
||||
}
|
||||
struct UseStateInner<T: 'static> {
|
||||
|
||||
@@ -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"]
|
||||
@@ -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 {
|
||||
@@ -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
File diff suppressed because it is too large.
Load diff
+5
-8
@@ -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;
|
||||
|
||||
|
||||
Reference in new issue
Block a user