feat: static node infrastructure and ssr changes

This commit is contained in:
Jonathan Kelley committed 2021-07-11 14:49:52 -04:00
1 parent 975fa566f9
commit 9abb0470b7
34 files changed
+1022 -855

No files matched your search

+10 -6
View File
@@ -9,24 +9,27 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi
[dependencies] [dependencies]
dioxus-core = { path="./packages/core", version="0.1.0" } dioxus-core = { path="./packages/core", version="0.1.0" }
dioxus-core-macro = { path="./packages/core-macro", version="0.1.0" } dioxus-core-macro = { path="./packages/core-macro", version="0.1.0", optional=true }
dioxus-html = { path="./packages/html", optional=true } dioxus-html = { path="./packages/html", optional=true }
dioxus-web = { path="./packages/web", optional=true } dioxus-web = { path="./packages/web", optional=true }
dioxus-webview = { path="./packages/webview", optional=true } dioxus-webview = { path="./packages/webview", optional=true }
dioxus-hooks = { path="./packages/hooks", version="0.0.0" } dioxus-hooks = { path="./packages/hooks", version="0.0.0", optional=true }
[features] [features]
default = ["ssr", "hooks", "router", "core", "atoms", "html", "web", "desktop"] default = ["macro", "ssr", "hooks", "router", "core", "atoms", "html", "web", "desktop"]
atoms = [] atoms = []
core = [] macro = ["dioxus-core-macro"]
ssr = [] ssr = []
hooks = [] hooks = ["dioxus-hooks"]
router = [] router = []
html = ["dioxus-html"] html = ["dioxus-html"]
web = ["dioxus-web"] web = ["dioxus-web"]
desktop = ["dioxus-webview"] desktop = ["dioxus-webview"]
# Core just includes dioxus-core (the only required package)
core = []
[profile.dev] [profile.dev]
split-debuginfo = "unpacked" split-debuginfo = "unpacked"
@@ -41,6 +44,7 @@ separator = "0.4.1"
serde = { version="1.0.126", features=["derive"] } serde = { version="1.0.126", features=["derive"] }
surf = "2.2.0" surf = "2.2.0"
env_logger = "*" env_logger = "*"
async-std = { version="1.9.0", features=["attributes"] }
[workspace] [workspace]
members = [ members = [
@@ -50,7 +54,7 @@ members = [
"packages/web", "packages/web",
"packages/webview", "packages/webview",
"packages/hooks", "packages/hooks",
"packages/ssr",
# "packages/atoms", # "packages/atoms",
# "packages/ssr",
# "packages/docsite", # "packages/docsite",
] ]
+6 -12
View File
@@ -36,14 +36,6 @@ If you know React, then you already know Dioxus.
- Powerful and simple integrated state management - Powerful and simple integrated state management
- Rust! (enums, static types, modules, efficiency) - Rust! (enums, static types, modules, efficiency)
### Key Differentiators
- Immutability by default
- Built-in suspense system
- Integrations for isomorphic apps
- Skip diffing altogether with signal API
- Extremely portable without runtime requirements
## Get Started with... ## Get Started with...
<table style="width:100%" align="center"> <table style="width:100%" align="center">
@@ -121,13 +113,15 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
| Controlled Inputs | ✅ | ✅ | stateful wrappers around inputs | | Controlled Inputs | ✅ | ✅ | stateful wrappers around inputs |
| CSS/Inline Styles | ✅ | ✅ | syntax for inline styles/attribute groups | | CSS/Inline Styles | ✅ | ✅ | syntax for inline styles/attribute groups |
| Custom elements | ✅ | ✅ | Define new element primitives | | Custom elements | ✅ | ✅ | Define new element primitives |
| Compile-time correct | ✅ | ✅ | Throw errors on invalid template layouts |
| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context |
| Suspense | 🛠 | ✅ | schedule future render from future/promise | | Suspense | 🛠 | ✅ | schedule future render from future/promise |
| Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events | | Cooperative Scheduling | 🛠 | ✅ | Prioritize important events over non-important events |
| Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates |
| Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) |
| NodeRef | 🛠 | ✅ | gain direct access to nodes [1] | | NodeRef | 🛠 | ✅ | gain direct access to nodes [1] |
| Fine-grained reactivity | 🛠 | ❓ | Skip diffing for fine-grain updates |
| Compile-time correct | ✅ | ❓ | Throw errors on invalid template layouts |
| Runs natively | ✅ | ❓ | runs as a portable binary w/o a runtime (Node) |
| 1st class global state | ✅ | ❓ | redux/recoil/mobx on top of context |
| Subtree Memoization | ✅ | ❓ | skip diffing static element subtrees |
| Heuristic Engine | ✅ | ❓ | track component memory usage to minimize allocations |
- [1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API. - [1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API.
+14 -12
View File
@@ -2,7 +2,7 @@
//! //!
//! The example from the README.md //! The example from the README.md
use std::pin::Pin; use std::{pin::Pin, time::Duration};
use dioxus::prelude::*; use dioxus::prelude::*;
use futures::Future; use futures::Future;
@@ -19,23 +19,25 @@ struct DogApi {
const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random"; const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random";
struct Ex(Pin<Box<dyn Future<Output = ()> + 'static>>);
static App: FC<()> = |cx| { static App: FC<()> = |cx| {
// let mut count = use_state(cx, || 0); let mut count = use_state(cx, || 0);
let mut fut = cx.use_hook( let mut fut = cx.use_hook(
move || { move || {
Ex(Box::pin(async { Box::pin(async {
// //
let mut tick = 0;
loop { loop {
match surf::get(ENDPOINT).recv_json::<DogApi>().await { async_std::task::sleep(Duration::from_millis(250)).await;
Ok(_) => (), log::debug!("ticking forward... {}", tick);
Err(_) => (), tick += 1;
} // match surf::get(ENDPOINT).recv_json::<DogApi>().await {
// Ok(_) => (),
// Err(_) => (),
// }
} }
}) }) as Pin<Box<dyn Future<Output = ()> + 'static>>
as Pin<Box<dyn Future<Output = ()> + 'static>>)
}, },
|h| &mut h.0, |h| h,
|_| {}, |_| {},
); );
@@ -43,7 +45,7 @@ static App: FC<()> = |cx| {
cx.render(rsx! { cx.render(rsx! {
div { div {
h1 {"it's working somewhat properly"}
} }
}) })
}; };
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"rust-analyzer.inlayHints.enable": true "rust-analyzer.inlayHints.enable": false
} }
+9 -10
View File
@@ -31,20 +31,19 @@ log = "0.4"
# # Serialize the Edits for use in Webview/Liveview instances # # Serialize the Edits for use in Webview/Liveview instances
serde = { version="1", features=["derive"], optional=true } serde = { version="1", features=["derive"], optional=true }
smallvec = "1.6.1"
# Backs scopes and unique keys # Backs scopes and unique keys
slotmap = "1.0.3" slotmap = "1.0.3"
# backs the fiber system for suspended components
# todo: would like to use something smaller or just roll our own futures manually
futures = "0.3.15"
async-std = { version="1.9.0", features=["attributes"] }
appendlist = "1.4.0" appendlist = "1.4.0"
futures-util = "0.3.15"
[dev-dependencies]
dioxus-html = { path="../html" }
dioxus-hooks = { path="../hooks" }
env_logger = "*"
# async-std = { version="1.9.0", features=["attributes"] }
[features] [features]
default = ["serialize"] default = []
# default = []
serialize = ["slotmap/serde", "serde"] serialize = ["slotmap/serde", "serde"]
+2 -2
View File
@@ -1,7 +1,7 @@
use std::pin::Pin; use std::pin::Pin;
use dioxus_core::prelude::*; use dioxus_core::prelude::*;
use futures::Future; use std::future::Future;
fn main() {} fn main() {}
@@ -18,7 +18,7 @@ const App: FC<()> = |cx| {
); );
// let g = unsafe { Pin::new_unchecked(fut) }; // let g = unsafe { Pin::new_unchecked(fut) };
cx.submit_task(fut); // cx.submit_task(fut);
todo!() todo!()
}; };
+2 -3
View File
@@ -2,7 +2,6 @@ use crate::hooklist::HookList;
use crate::{arena::SharedArena, innerlude::*}; use crate::{arena::SharedArena, innerlude::*};
use appendlist::AppendList; use appendlist::AppendList;
use bumpalo::Bump; use bumpalo::Bump;
use futures::FutureExt;
use slotmap::DefaultKey; use slotmap::DefaultKey;
use slotmap::SlotMap; use slotmap::SlotMap;
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -34,11 +33,11 @@ impl ActiveFrame {
Self::from_frames( Self::from_frames(
BumpFrame { BumpFrame {
bump: Bump::new(), bump: Bump::new(),
head_node: VNode::text(""), head_node: VNode::static_text(""),
}, },
BumpFrame { BumpFrame {
bump: Bump::new(), bump: Bump::new(),
head_node: VNode::text(""), head_node: VNode::static_text(""),
}, },
) )
} }
+164
View File
@@ -0,0 +1,164 @@
/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
///
/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
/// "InsertBefore".
struct RealChildIterator<'a> {
scopes: &'a SharedArena,
// Heuristcally we should never bleed into 5 completely nested fragments/components
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
}
impl<'a> RealChildIterator<'a> {
fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self {
Self {
scopes,
stack: smallvec::smallvec![(0, starter)],
}
}
}
// impl<'a> DoubleEndedIterator for ChildIterator<'a> {
// fn next_back(&mut self) -> Option<Self::Item> {
// todo!()
// }
// }
impl<'a> Iterator for RealChildIterator<'a> {
type Item = &'a VNode<'a>;
fn next(&mut self) -> Option<&'a VNode<'a>> {
let mut should_pop = false;
let mut returned_node = None;
let mut should_push = None;
while returned_node.is_none() {
if let Some((count, node)) = self.stack.last_mut() {
match node {
// We can only exit our looping when we get "real" nodes
VNode::Element(_) | VNode::Text(_) => {
// We've recursed INTO an element/text
// We need to recurse *out* of it and move forward to the next
should_pop = true;
returned_node = Some(&**node);
}
// If we get a fragment we push the next child
VNode::Fragment(frag) => {
let _count = *count as usize;
if _count >= frag.children.len() {
should_pop = true;
} else {
should_push = Some(&frag.children[_count]);
}
}
// Immediately abort suspended nodes - can't do anything with them yet
// VNode::Suspended => should_pop = true,
VNode::Suspended { real } => todo!(),
// For components, we load their root and push them onto the stack
VNode::Component(sc) => {
let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap();
// Simply swap the current node on the stack with the root of the component
*node = scope.root();
}
}
} else {
// If there's no more items on the stack, we're done!
return None;
}
if should_pop {
self.stack.pop();
if let Some((id, _)) = self.stack.last_mut() {
*id += 1;
}
should_pop = false;
}
if let Some(push) = should_push {
self.stack.push((0, push));
should_push = None;
}
}
returned_node
}
}
mod tests {
use super::*;
use crate as dioxus;
use crate::innerlude::*;
use crate::util::DebugDom;
use dioxus_core_macro::*;
// #[test]
// fn test_child_iterator() {
// static App: FC<()> = |cx| {
// cx.render(rsx! {
// Fragment {
// div {}
// h1 {}
// h2 {}
// h3 {}
// Fragment {
// "internal node"
// div {
// "baller text shouldn't show up"
// }
// p {
// }
// Fragment {
// Fragment {
// "wow you really like framgents"
// Fragment {
// "why are you like this"
// Fragment {
// "just stop now please"
// Fragment {
// "this hurts"
// Fragment {
// "who needs this many fragments?????"
// Fragment {
// "just... fine..."
// Fragment {
// "no"
// }
// }
// }
// }
// }
// }
// }
// }
// }
// "my text node 1"
// "my text node 2"
// "my text node 3"
// "my text node 4"
// }
// })
// };
// let mut dom = VirtualDom::new(App);
// let mut renderer = DebugDom::new();
// dom.rebuild(&mut renderer).unwrap();
// let starter = dom.base_scope().root();
// let ite = RealChildIterator::new(starter, &dom.components);
// for child in ite {
// match child {
// VNode::Element(el) => println!("Found: Element {}", el.tag_name),
// VNode::Text(t) => println!("Found: Text {:?}", t.text),
// // These would represent failing cases.
// VNode::Fragment(_) => panic!("Found: Fragment"),
// VNode::Suspended { real } => panic!("Found: Suspended"),
// VNode::Component(_) => panic!("Found: Component"),
// }
// }
// }
}
+2
View File
@@ -9,6 +9,7 @@ use crate::innerlude::FC;
pub trait Properties: Sized { pub trait Properties: Sized {
type Builder; type Builder;
const IS_STATIC: bool = false;
fn builder() -> Self::Builder; fn builder() -> Self::Builder;
/// Memoization can only happen if the props are 'static /// Memoization can only happen if the props are 'static
@@ -19,6 +20,7 @@ pub trait Properties: Sized {
impl Properties for () { impl Properties for () {
type Builder = EmptyBuilder; type Builder = EmptyBuilder;
const IS_STATIC: bool = true;
fn builder() -> Self::Builder { fn builder() -> Self::Builder {
EmptyBuilder {} EmptyBuilder {}
} }
+30 -31
View File
@@ -2,7 +2,6 @@ use crate::hooklist::HookList;
use crate::{arena::SharedArena, innerlude::*}; use crate::{arena::SharedArena, innerlude::*};
use appendlist::AppendList; use appendlist::AppendList;
use bumpalo::Bump; use bumpalo::Bump;
use futures::FutureExt;
use slotmap::DefaultKey; use slotmap::DefaultKey;
use slotmap::SlotMap; use slotmap::SlotMap;
use std::marker::PhantomData; use std::marker::PhantomData;
@@ -40,11 +39,9 @@ use std::{
pub struct Context<'src, T> { pub struct Context<'src, T> {
pub props: &'src T, pub props: &'src T,
pub scope: &'src Scope, pub scope: &'src Scope,
pub tasks: &'src AppendList<&'src mut DTask<'src>>, pub tasks: &'src RefCell<Vec<&'src mut PinnedTask>>,
} }
pub type PinnedTask = Pin<Box<dyn Future<Output = ()>>>;
pub type DTask<'s> = dyn Future<Output = ()> + 's;
// pub type DTask = Pin<Box<dyn Future<Output = ()>>>;
impl<'src, T> Copy for Context<'src, T> {} impl<'src, T> Copy for Context<'src, T> {}
impl<'src, T> Clone for Context<'src, T> { impl<'src, T> Clone for Context<'src, T> {
@@ -133,36 +130,34 @@ impl<'src, P> Context<'src, P> {
/// ) /// )
/// } /// }
/// ``` /// ```
pub fn use_hook<InternalHookState: 'static, Output: 'src>( pub fn use_hook<State, Output, Init, Run, Cleanup>(
self, self,
initializer: Init,
// The closure that builds the hook state runner: Run,
initializer: impl FnOnce() -> InternalHookState, cleanup: Cleanup,
) -> Output
// The closure that takes the hookstate and returns some value where
runner: impl FnOnce(&'src mut InternalHookState) -> Output, State: 'static,
Output: 'src,
// The closure that cleans up whatever mess is left when the component gets torn down Init: FnOnce() -> State,
// TODO: add this to the "clean up" group for when the component is dropped Run: FnOnce(&'src mut State) -> Output,
cleanup: impl FnOnce(InternalHookState), Cleanup: FnOnce(State),
) -> Output { {
// If the idx is the same as the hook length, then we need to add the current hook // If the idx is the same as the hook length, then we need to add the current hook
if self.scope.hooks.at_end() { if self.scope.hooks.at_end() {
let new_state = initializer(); let new_state = initializer();
self.scope.hooks.push(new_state); self.scope.hooks.push(new_state);
} }
let state = self.scope.hooks.next::<InternalHookState>().expect( const ERR_MSG: &str = r###"
r###" Unable to retrive the hook that was initialized in this index.
Unable to retrive the hook that was initialized in this index. Consult the `rules of hooks` to understand how to use hooks properly.
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.
Any function prefixed with "use" should not be called conditionally.
"###,
);
runner(state) You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
Any function prefixed with "use" should not be called conditionally.
"###;
runner(self.scope.hooks.next::<State>().expect(ERR_MSG))
} }
/// This hook enables the ability to expose state to children further down the VirtualDOM Tree. /// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
@@ -178,8 +173,7 @@ impl<'src, P> Context<'src, P> {
/// ///
/// ///
pub fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) { pub fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) {
let mut scope = self.scope; let mut cxs = self.scope.shared_contexts.borrow_mut();
let mut cxs = scope.shared_contexts.borrow_mut();
let ty = TypeId::of::<T>(); let ty = TypeId::of::<T>();
let is_initialized = self.use_hook( let is_initialized = self.use_hook(
@@ -274,6 +268,7 @@ impl<'src, P> Context<'src, P> {
fut: &'src mut Pin<Box<dyn Future<Output = Output> + 'static>>, fut: &'src mut Pin<Box<dyn Future<Output = Output> + 'static>>,
callback: Fut, callback: Fut,
) -> VNode<'src> { ) -> VNode<'src> {
use futures_util::FutureExt;
match fut.now_or_never() { match fut.now_or_never() {
Some(out) => { Some(out) => {
let suspended_cx = SuspendedContext {}; let suspended_cx = SuspendedContext {};
@@ -290,6 +285,10 @@ impl<'src, P> Context<'src, P> {
} }
/// `submit_task` will submit the future to be polled. /// `submit_task` will submit the future to be polled.
///
/// This is useful when you have some async task that needs to be progressed.
///
/// ## Explanation
/// Dioxus will step its internal event loop if the future returns if the future completes while waiting. /// Dioxus will step its internal event loop if the future returns if the future completes while waiting.
/// ///
/// Tasks can't return anything, but they can be controlled with the returned handle /// Tasks can't return anything, but they can be controlled with the returned handle
@@ -299,8 +298,8 @@ impl<'src, P> Context<'src, P> {
/// ///
/// ///
/// ///
pub fn submit_task(&self, mut task: &'src mut DTask<'src>) -> TaskHandle { pub fn submit_task(&self, task: &'src mut PinnedTask) -> TaskHandle {
self.tasks.push(task); self.tasks.borrow_mut().push(task);
TaskHandle { _p: PhantomData {} } TaskHandle { _p: PhantomData {} }
} }
+143 -237
View File
@@ -5,14 +5,31 @@
//! ------ //! ------
//! //!
//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support //! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
//! Components, Fragments, Suspense, and additional batching operations. //! Components, Fragments, Suspense, SubTree memoization, and additional batching operations.
//! //!
//! Implementation Details: //! ## Implementation Details:
//! ----------------------- //!
//! ### IDs for elements
//! //!
//! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes. //! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
//! We don't necessarily require that DOM changes happen instnatly during the diffing process, so the implementor may choose //! We don't necessarily require that DOM changes happen instnatly during the diffing process, so the implementor may choose
//! to batch nodes if it is more performant for their application. We care about an ID size of u32 //! to batch nodes if it is more performant for their application. The expectation is that renderers use a Slotmap for nodes
//! whose keys can be converted to u64 on FFI boundaries.
//!
//! When new nodes are created through `render`, they won't know which real node they correspond to. During diffing, we
//! always make sure to copy over the ID. If we don't do this properly, the realdomnode will be populated incorrectly and
//! brick the user's page.
//!
//! ## Subtree Memoization
//! We also employ "subtree memoization" which saves us from having to check trees which take no dynamic content. We can
//! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the
//! calls to "create" propogate this information upwards. Structures like the one below are entirely static:
//! ```rust
//! rsx!( div { class: "hello world", "this node is entirely static" } )
//! ```
//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so its up to the reconciler to
//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP
//!
//! //!
//! //!
//! Further Reading and Thoughts //! Further Reading and Thoughts
@@ -25,10 +42,7 @@
use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue}; use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue};
use fxhash::{FxHashMap, FxHashSet}; use fxhash::{FxHashMap, FxHashSet};
use std::{ use std::any::Any;
any::Any,
rc::{Rc, Weak},
};
/// The accompanying "real dom" exposes an imperative API for controlling the UI layout /// The accompanying "real dom" exposes an imperative API for controlling the UI layout
/// ///
@@ -46,8 +60,10 @@ pub trait RealDom<'a> {
fn push_root(&mut self, root: RealDomNode); fn push_root(&mut self, root: RealDomNode);
// Add Nodes to the dom // Add Nodes to the dom
fn append_child(&mut self); // add m nodes from the stack
fn replace_with(&mut self); fn append_children(&mut self, many: u32);
// replace the n-m node on the stack with the m nodes
fn replace_with(&mut self, many: u32);
// Remove Nodesfrom the dom // Remove Nodesfrom the dom
fn remove(&mut self); fn remove(&mut self);
@@ -101,7 +117,10 @@ pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
pub seen_nodes: FxHashSet<ScopeIdx>, pub seen_nodes: FxHashSet<ScopeIdx>,
} }
impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> { impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom>
where
Dom: RealDom<'bump>,
{
pub fn new( pub fn new(
dom: &'real mut Dom, dom: &'real mut Dom,
components: &'bump SharedArena, components: &'bump SharedArena,
@@ -141,6 +160,10 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment". // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable. // So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
(VNode::Text(old), VNode::Text(new)) => { (VNode::Text(old), VNode::Text(new)) => {
if old.is_static {
log::debug!("encountered static text node: {:?}", old.text);
}
if old.text != new.text { if old.text != new.text {
self.dom.push_root(old.dom_id.get()); self.dom.push_root(old.dom_id.get());
log::debug!("Text has changed {}, {}", old.text, new.text); log::debug!("Text has changed {}, {}", old.text, new.text);
@@ -156,8 +179,8 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
// In Dioxus, this is less likely to occur unless through a fragment // In Dioxus, this is less likely to occur unless through a fragment
if new.tag_name != old.tag_name || new.namespace != old.namespace { if new.tag_name != old.tag_name || new.namespace != old.namespace {
self.dom.push_root(old.dom_id.get()); self.dom.push_root(old.dom_id.get());
self.create(new_node); let meta = self.create(new_node);
self.dom.replace_with(); self.dom.replace_with(meta.added_to_stack);
return; return;
} }
new.dom_id.set(old.dom_id.get()); new.dom_id.set(old.dom_id.get());
@@ -181,7 +204,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
// make sure the component's caller function is up to date // make sure the component's caller function is up to date
let scope = self.components.try_get_mut(scope_id.unwrap()).unwrap(); let scope = self.components.try_get_mut(scope_id.unwrap()).unwrap();
// .with_scope(scope_id.unwrap(), |scope| { // .with_scope(scope_id.unwrap(), |scope| {
scope.caller = Rc::downgrade(&new.caller); scope.caller = new.caller.clone();
// ack - this doesn't work on its own! // ack - this doesn't work on its own!
scope.update_children(new.children); scope.update_children(new.children);
@@ -265,17 +288,17 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
// in the case where the old node was a fragment but the new nodes are text, // in the case where the old node was a fragment but the new nodes are text,
(VNode::Fragment(_) | VNode::Component(_), VNode::Element(_) | VNode::Text(_)) => { (VNode::Fragment(_) | VNode::Component(_), VNode::Element(_) | VNode::Text(_)) => {
// find the first real element int the old node // find the first real element int the old node
let mut iter = RealChildIterator::new(old_node, self.components); // let mut iter = RealChildIterator::new(old_node, self.components);
if let Some(first) = iter.next() { // if let Some(first) = iter.next() {
// replace the very first node with the creation of the element or text // // replace the very first node with the creation of the element or text
} else { // } else {
// there are no real elements in the old fragment... // // there are no real elements in the old fragment...
// We need to load up the next real // // We need to load up the next real
} // }
if let VNode::Component(old) = old_node { // if let VNode::Component(old) = old_node {
// schedule this component's destructor to be run // // schedule this component's destructor to be run
todo!() // todo!()
} // }
} }
// In the case where real nodes are being replaced by potentially // In the case where real nodes are being replaced by potentially
(VNode::Element(_) | VNode::Text(_), VNode::Fragment(new)) => { (VNode::Element(_) | VNode::Text(_), VNode::Fragment(new)) => {
@@ -283,11 +306,11 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
} }
(VNode::Text(_), VNode::Element(_)) => { (VNode::Text(_), VNode::Element(_)) => {
self.create(new_node); self.create(new_node);
self.dom.replace_with(); self.dom.replace_with(1);
} }
(VNode::Element(_), VNode::Text(_)) => { (VNode::Element(_), VNode::Text(_)) => {
self.create(new_node); self.create(new_node);
self.dom.replace_with(); self.dom.replace_with(1);
} }
_ => { _ => {
@@ -295,7 +318,28 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
} }
} }
} }
}
// When we create new nodes, we need to propagate some information back up the call chain.
// This gives the caller some information on how to handle things like insertins, appending, and subtree discarding.
struct CreateMeta {
is_static: bool,
added_to_stack: u32,
}
impl CreateMeta {
fn new(is_static: bool, added_to_tack: u32) -> Self {
Self {
is_static,
added_to_stack: added_to_tack,
}
}
}
impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom>
where
Dom: RealDom<'bump>,
{
// Emit instructions to create the given virtual node. // Emit instructions to create the given virtual node.
// //
// The change list stack may have any shape upon entering this function: // The change list stack may have any shape upon entering this function:
@@ -305,15 +349,22 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
// When this function returns, the new node is on top of the change list stack: // When this function returns, the new node is on top of the change list stack:
// //
// [... node] // [... node]
fn create(&mut self, node: &'bump VNode<'bump>) { fn create(&mut self, node: &'bump VNode<'bump>) -> CreateMeta {
// debug_assert!(self.dom.traversal_is_committed()); log::warn!("Creating node! ... {:#?}", node);
log::warn!("Creating node!");
match node { match node {
VNode::Text(text) => { VNode::Text(text) => {
let real_id = self.dom.create_text_node(text.text); let real_id = self.dom.create_text_node(text.text);
text.dom_id.set(real_id); text.dom_id.set(real_id);
CreateMeta::new(text.is_static, 1)
} }
VNode::Element(el) => { VNode::Element(el) => {
// we have the potential to completely eliminate working on this node in the future(!)
//
// This can only be done if all of the elements properties (attrs, children, listeners, etc) are static
// While creating these things, keep track if we can memoize this element.
// At the end, we'll set this flag on the element to skip it
let mut is_static: bool = true;
let VElement { let VElement {
key, key,
tag_name, tag_name,
@@ -322,22 +373,27 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
children, children,
namespace, namespace,
dom_id, dom_id,
is_static: el_is_static,
} = el; } = el;
// log::info!("Creating {:#?}", node);
let real_id = if let Some(namespace) = namespace { let real_id = if let Some(namespace) = namespace {
self.dom.create_element(tag_name, Some(namespace)) self.dom.create_element(tag_name, Some(namespace))
} else { } else {
self.dom.create_element(tag_name, None) self.dom.create_element(tag_name, None)
}; };
el.dom_id.set(real_id); dom_id.set(real_id);
listeners.iter().enumerate().for_each(|(idx, listener)| { listeners.iter().enumerate().for_each(|(idx, listener)| {
listener.mounted_node.set(real_id);
self.dom self.dom
.new_event_listener(listener.event, listener.scope, idx, real_id); .new_event_listener(listener.event, listener.scope, idx, real_id);
listener.mounted_node.set(real_id);
// if the node has an event listener, then it must be visited ?
is_static = false;
}); });
for attr in *attributes { for attr in *attributes {
is_static = is_static && attr.is_static;
self.dom.set_attribute(&attr.name, &attr.value, *namespace); self.dom.set_attribute(&attr.name, &attr.value, *namespace);
} }
@@ -359,28 +415,26 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
// } // }
for child in *children { for child in *children {
self.create(child); let child_meta = self.create(child);
if let VNode::Fragment(_) = child { is_static = is_static && child_meta.is_static;
// do nothing
// fragments append themselves // append whatever children were generated by this call
} else { self.dom.append_children(child_meta.added_to_stack);
self.dom.append_child();
}
} }
if is_static {
log::debug!("created a static node {:#?}", node);
} else {
log::debug!("created a dynamic node {:#?}", node);
}
el_is_static.set(is_static);
CreateMeta::new(is_static, 1)
} }
VNode::Component(component) => { VNode::Component(vcomponent) => {
// let real_id = self.dom.create_placeholder();
// let root_id = next_id();
// self.dom.save_known_root(root_id);
log::debug!("Mounting a new component"); log::debug!("Mounting a new component");
let caller: Weak<WrappedCaller> = Rc::downgrade(&component.caller); let caller = vcomponent.caller.clone();
// We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
// those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
// However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
let parent_idx = self.cur_idx; let parent_idx = self.cur_idx;
@@ -398,50 +452,69 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
height, height,
self.event_queue.new_channel(height, new_idx), self.event_queue.new_channel(height, new_idx),
self.components.clone(), self.components.clone(),
component.children, vcomponent.children,
self.task_queue.new_submitter(),
) )
}) })
}) })
.unwrap(); .unwrap();
{ // This code is supposed to insert the new idx into the parent's descendent list, but it doesn't really work.
let cur_component = self.components.try_get_mut(idx).unwrap(); // This is mostly used for cleanup - to remove old scopes when components are destroyed.
let mut ch = cur_component.descendents.borrow_mut(); // TODO
ch.insert(idx); //
std::mem::drop(ch); // self.components
} // .try_get_mut(idx)
// .unwrap()
// .descendents
// .borrow_mut()
// .insert(idx);
// yaaaaay lifetimes out of thin air // TODO: abstract this unsafe into the arena abstraction
// really tho, we're merging the frame lifetimes together
let inner: &'bump mut _ = unsafe { &mut *self.components.components.get() }; let inner: &'bump mut _ = unsafe { &mut *self.components.components.get() };
let new_component = inner.get_mut(idx).unwrap(); let new_component = inner.get_mut(idx).unwrap();
// Actually initialize the caller's slot with the right address // Actually initialize the caller's slot with the right address
component.ass_scope.set(Some(idx)); vcomponent.ass_scope.set(Some(idx));
// Run the scope for one iteration to initialize it // Run the scope for one iteration to initialize it
new_component.run_scope().unwrap(); new_component.run_scope().unwrap();
// And then run the diff algorithm // By using "diff_node" instead of "create", we delegate the mutations to the child
let _real_id = self.dom.create_placeholder(); // However, "diff_node" always expects a real node on the stack, so we put a placeholder so it knows where to start.
//
// TODO: we need to delete (IE relcaim this node, otherwise the arena will grow infinitely)
let _ = self.dom.create_placeholder();
self.diff_node(new_component.old_frame(), new_component.next_frame()); self.diff_node(new_component.old_frame(), new_component.next_frame());
// Finally, insert this node as a seen node. // Finally, insert this node as a seen node.
self.seen_nodes.insert(idx); self.seen_nodes.insert(idx);
// Virtual Components don't result in new nodes on the stack
// However, we can skip them from future diffing if they take no children, have no props, take no key, etc.
CreateMeta::new(vcomponent.is_static, 0)
} }
// we go the the "known root" but only operate on a sibling basis // Fragments are the only nodes that can contain dynamic content (IE through curlies or iterators).
// We can never ignore their contents, so the prescence of a fragment indicates that we need always diff them.
VNode::Fragment(frag) => { VNode::Fragment(frag) => {
// create the children directly in the space let mut nodes_added = 0;
for child in frag.children { for child in frag.children.iter().rev() {
self.create(child); // different types of nodes will generate different amounts on the stack
self.dom.append_child(); // nested fragments will spew a ton of nodes onto the stack
// TODO: make sure that our order (.rev) makes sense in a nested situation
let new_meta = self.create(child);
nodes_added += new_meta.added_to_stack;
} }
// Never ignore
CreateMeta::new(false, nodes_added)
} }
VNode::Suspended { real } => { VNode::Suspended { real } => {
let id = self.dom.create_placeholder(); let id = self.dom.create_placeholder();
real.set(id); real.set(id);
CreateMeta::new(false, 1)
} }
} }
} }
@@ -1171,11 +1244,9 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
// //
// When this function returns, the change list stack is in the same state. // When this function returns, the change list stack is in the same state.
pub fn create_and_append_children(&mut self, new: &'bump [VNode<'bump>]) { pub fn create_and_append_children(&mut self, new: &'bump [VNode<'bump>]) {
// debug_assert!(self.dom.traversal_is_committed());
for child in new { for child in new {
// self.create_and_append(node, parent) let meta = self.create(child);
self.create(child); self.dom.append_children(meta.added_to_stack);
self.dom.append_child();
} }
} }
@@ -1227,168 +1298,3 @@ enum KeyedPrefixResult {
// the beginning of `new` and `old` we already processed. // the beginning of `new` and `old` we already processed.
MoreWorkToDo(usize), MoreWorkToDo(usize),
} }
/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
///
/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
/// "InsertBefore".
struct RealChildIterator<'a> {
scopes: &'a SharedArena,
// Heuristcally we should never bleed into 5 completely nested fragments/components
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
}
impl<'a> RealChildIterator<'a> {
fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self {
Self {
scopes,
stack: smallvec::smallvec![(0, starter)],
}
}
}
// impl<'a> DoubleEndedIterator for ChildIterator<'a> {
// fn next_back(&mut self) -> Option<Self::Item> {
// todo!()
// }
// }
impl<'a> Iterator for RealChildIterator<'a> {
type Item = &'a VNode<'a>;
fn next(&mut self) -> Option<&'a VNode<'a>> {
let mut should_pop = false;
let mut returned_node = None;
let mut should_push = None;
while returned_node.is_none() {
if let Some((count, node)) = self.stack.last_mut() {
match node {
// We can only exit our looping when we get "real" nodes
VNode::Element(_) | VNode::Text(_) => {
// We've recursed INTO an element/text
// We need to recurse *out* of it and move forward to the next
should_pop = true;
returned_node = Some(&**node);
}
// If we get a fragment we push the next child
VNode::Fragment(frag) => {
let _count = *count as usize;
if _count >= frag.children.len() {
should_pop = true;
} else {
should_push = Some(&frag.children[_count]);
}
}
// Immediately abort suspended nodes - can't do anything with them yet
// VNode::Suspended => should_pop = true,
VNode::Suspended { real } => todo!(),
// For components, we load their root and push them onto the stack
VNode::Component(sc) => {
let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap();
// Simply swap the current node on the stack with the root of the component
*node = scope.root();
}
}
} else {
// If there's no more items on the stack, we're done!
return None;
}
if should_pop {
self.stack.pop();
if let Some((id, _)) = self.stack.last_mut() {
*id += 1;
}
should_pop = false;
}
if let Some(push) = should_push {
self.stack.push((0, push));
should_push = None;
}
}
returned_node
}
}
mod tests {
use super::*;
use crate as dioxus;
use crate::innerlude::*;
use crate::util::DebugDom;
use dioxus_core_macro::*;
// #[test]
// fn test_child_iterator() {
// static App: FC<()> = |cx| {
// cx.render(rsx! {
// Fragment {
// div {}
// h1 {}
// h2 {}
// h3 {}
// Fragment {
// "internal node"
// div {
// "baller text shouldn't show up"
// }
// p {
// }
// Fragment {
// Fragment {
// "wow you really like framgents"
// Fragment {
// "why are you like this"
// Fragment {
// "just stop now please"
// Fragment {
// "this hurts"
// Fragment {
// "who needs this many fragments?????"
// Fragment {
// "just... fine..."
// Fragment {
// "no"
// }
// }
// }
// }
// }
// }
// }
// }
// }
// "my text node 1"
// "my text node 2"
// "my text node 3"
// "my text node 4"
// }
// })
// };
// let mut dom = VirtualDom::new(App);
// let mut renderer = DebugDom::new();
// dom.rebuild(&mut renderer).unwrap();
// let starter = dom.base_scope().root();
// let ite = RealChildIterator::new(starter, &dom.components);
// for child in ite {
// match child {
// VNode::Element(el) => println!("Found: Element {}", el.tag_name),
// VNode::Text(t) => println!("Found: Text {:?}", t.text),
// // These would represent failing cases.
// VNode::Fragment(_) => panic!("Found: Fragment"),
// VNode::Suspended { real } => panic!("Found: Suspended"),
// VNode::Component(_) => panic!("Found: Component"),
// }
// }
// }
}
+1 -1
View File
@@ -4,7 +4,7 @@
//! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might //! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might
//! be heavy or need to interact through FFI, so the events themselves are designed to be lazy. //! be heavy or need to interact through FFI, so the events themselves are designed to be lazy.
use std::{ops::Deref, rc::Rc}; use std::{cell::Cell, ops::Deref, rc::Rc};
use crate::innerlude::{RealDomNode, ScopeIdx}; use crate::innerlude::{RealDomNode, ScopeIdx};
+2
View File
@@ -43,6 +43,7 @@ pub(crate) mod innerlude {
pub use crate::nodebuilder::*; pub use crate::nodebuilder::*;
pub use crate::nodes::*; pub use crate::nodes::*;
pub use crate::scope::*; pub use crate::scope::*;
pub use crate::tasks::*;
pub use crate::util::*; pub use crate::util::*;
pub use crate::virtual_dom::*; pub use crate::virtual_dom::*;
@@ -62,6 +63,7 @@ pub mod prelude {
pub use crate::styles::{AsAttr, StyleBuilder}; pub use crate::styles::{AsAttr, StyleBuilder};
pub use crate::util::RealDomNode; pub use crate::util::RealDomNode;
pub use crate::virtual_dom::VirtualDom;
pub use nodes::*; pub use nodes::*;
pub use crate::nodebuilder::LazyNodes; pub use crate::nodebuilder::LazyNodes;
+73 -154
View File
@@ -308,7 +308,7 @@ where
/// let my_div: VNode = builder.finish(); /// let my_div: VNode = builder.finish();
/// ``` /// ```
#[inline] #[inline]
pub fn finish(self) -> VNode<'a> { pub fn finish(mut self) -> VNode<'a> {
let bump = self.cx.bump(); let bump = self.cx.bump();
let children: &'a Children = bump.alloc(self.children); let children: &'a Children = bump.alloc(self.children);
@@ -317,6 +317,23 @@ where
let listeners: &'a Listeners = bump.alloc(self.listeners); let listeners: &'a Listeners = bump.alloc(self.listeners);
let listeners: &'a [Listener<'a>] = listeners.as_ref(); let listeners: &'a [Listener<'a>] = listeners.as_ref();
for listener in listeners {
// bump the context id forward
let id = self.cx.listener_id.get();
self.cx.listener_id.set(id + 1);
// Add this listener to the context list
// This casts the listener to a self-referential pointer
// This is okay because the bump arena is stable
// TODO: maybe not add it into CTX immediately
let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(listener) };
self.cx.scope_ref.listeners.borrow_mut().push((
r.mounted_node as *const _ as *mut _,
r.callback as *const _ as *mut _,
));
}
let attributes: &'a Attributes = bump.alloc(self.attributes); let attributes: &'a Attributes = bump.alloc(self.attributes);
let attributes: &'a [Attribute<'a>] = attributes.as_ref(); let attributes: &'a [Attribute<'a>] = attributes.as_ref();
@@ -338,57 +355,57 @@ where
Attributes: 'a + AsRef<[Attribute<'a>]>, Attributes: 'a + AsRef<[Attribute<'a>]>,
Children: 'a + AsRef<[VNode<'a>]>, Children: 'a + AsRef<[VNode<'a>]>,
{ {
/// Add a new event listener to this element. // / Add a new event listener to this element.
/// // /
/// The `event` string specifies which event will be listened for. The // / The `event` string specifies which event will be listened for. The
/// `callback` function is the function that will be invoked if the // / `callback` function is the function that will be invoked if the
/// specified event occurs. // / specified event occurs.
/// // /
/// # Example // / # Example
/// // /
/// ```no_run // / ```no_run
/// use dioxus::{builder::*, bumpalo::Bump}; // / use dioxus::{builder::*, bumpalo::Bump};
/// // /
/// let b = Bump::new(); // / let b = Bump::new();
/// // /
/// // A button that does something when clicked! // / // A button that does something when clicked!
/// let my_button = button(&b) // / let my_button = button(&b)
/// .on("click", |event| { // / .on("click", |event| {
/// // ... // / // ...
/// }) // / })
/// .finish(); // / .finish();
/// ``` // / ```
pub fn on(self, event: &'static str, callback: impl FnMut(VirtualEvent) + 'a) -> Self { // pub fn on(self, event: &'static str, callback: impl FnMut(VirtualEvent) + 'a) -> Self {
let bump = &self.cx.bump(); // let bump = &self.cx.bump();
let listener = Listener { // let listener = Listener {
event, // event,
callback: bump.alloc(callback), // callback: bump.alloc(callback),
scope: self.cx.scope_ref.arena_idx, // scope: self.cx.scope_ref.arena_idx,
mounted_node: bump.alloc(Cell::new(RealDomNode::empty())), // mounted_node: bump.alloc(Cell::new(RealDomNode::empty())),
}; // };
self.add_listener(listener) // self.add_listener(listener)
} // }
pub fn add_listener(mut self, listener: Listener<'a>) -> Self { // pub fn add_listener(mut self, listener: Listener<'a>) -> Self {
self.listeners.push(listener); // self.listeners.push(listener);
// bump the context id forward // // bump the context id forward
let id = self.cx.listener_id.get(); // let id = self.cx.listener_id.get();
self.cx.listener_id.set(id + 1); // self.cx.listener_id.set(id + 1);
// Add this listener to the context list // // Add this listener to the context list
// This casts the listener to a self-referential pointer // // This casts the listener to a self-referential pointer
// This is okay because the bump arena is stable // // This is okay because the bump arena is stable
self.listeners.last().map(|g| { // self.listeners.last().map(|g| {
let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(g) }; // let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(g) };
self.cx.scope_ref.listeners.borrow_mut().push(( // self.cx.scope_ref.listeners.borrow_mut().push((
r.mounted_node as *const _ as *mut _, // r.mounted_node as *const _ as *mut _,
r.callback as *const _ as *mut _, // r.callback as *const _ as *mut _,
)); // ));
}); // });
self // self
} // }
} }
impl<'a, 'b, Listeners, Children> impl<'a, 'b, Listeners, Children>
@@ -410,61 +427,16 @@ where
/// let my_div = div(&b).attr("id", "my-div").finish(); /// let my_div = div(&b).attr("id", "my-div").finish();
/// ``` /// ```
pub fn attr(mut self, name: &'static str, args: std::fmt::Arguments) -> Self { pub fn attr(mut self, name: &'static str, args: std::fmt::Arguments) -> Self {
let value = match args.as_str() { let (value, is_static) = raw_text(self.cx.bump(), args);
Some(static_str) => static_str,
None => {
use bumpalo::core_alloc::fmt::Write;
let mut s = bumpalo::collections::String::new_in(self.cx.bump());
s.write_fmt(args).unwrap();
s.into_bump_str()
}
};
self.attributes.push(Attribute { self.attributes.push(Attribute {
name, name,
value, value,
is_static,
namespace: None, namespace: None,
}); });
self self
} }
/// Conditionally add a "boolean-style" attribute to this element.
///
/// If the `should_add` parameter is true, then adds an attribute with the
/// given `name` and an empty string value. If the `should_add` parameter is
/// false, then the attribute is not added.
///
/// This method is useful for attributes whose semantics are defined by
/// whether or not the attribute is present or not, and whose value is
/// ignored. Example attributes like this include:
///
/// * `checked`
/// * `hidden`
/// * `selected`
///
/// # Example
///
/// ```no_run
/// use dioxus::{builder::*, bumpalo::Bump};
/// use js_sys::Math;
///
/// let b = Bump::new();
///
/// // Create the `<div>` that is randomly hidden 50% of the time.
/// let my_div = div(&b)
/// .bool_attr("hidden", Math::random() >= 0.5)
/// .finish();
/// ```
pub fn bool_attr(mut self, name: &'static str, should_add: bool) -> Self {
if should_add {
self.attributes.push(Attribute {
name,
value: "",
namespace: None,
});
}
self
}
} }
impl<'a, 'b, Listeners, Attributes> impl<'a, 'b, Listeners, Attributes>
@@ -618,72 +590,18 @@ impl IntoVNode<'_> for Option<()> {
} }
} }
/// Construct a text VNode. pub fn raw_text<'a>(bump: &'a bumpalo::Bump, args: std::fmt::Arguments) -> (&'a str, bool) {
///
/// This is `dioxus`'s virtual DOM equivalent of `document.createTextVNode`.
///
/// # Example
///
/// ```no_run
/// use dioxus::builder::*;
///
/// let my_text = text("hello, dioxus!");
/// ```
#[inline]
pub fn text<'a>(contents: &'a str) -> VNode<'a> {
VNode::text(contents)
}
pub fn text2<'a>(contents: bumpalo::collections::String<'a>) -> VNode<'a> {
let f: &'a str = contents.into_bump_str();
VNode::text(f)
}
pub fn text3<'a>(bump: &'a bumpalo::Bump, args: std::fmt::Arguments) -> VNode<'a> {
// This is a cute little optimization
//
// We use format_args! on everything. However, not all textnodes have dynamic content, and thus are completely static.
// we can just short-circuit to the &'static str version instead of having to allocate in the bump arena.
//
// In the most general case, this prevents the need for any string allocations for simple code IE:
// ```
// div {"abc"}
// ```
VNode::text(raw_text(bump, args))
}
pub fn raw_text<'a>(bump: &'a bumpalo::Bump, args: std::fmt::Arguments) -> &'a str {
match args.as_str() { match args.as_str() {
Some(static_str) => static_str, Some(static_str) => (static_str, true),
None => { None => {
use bumpalo::core_alloc::fmt::Write; use bumpalo::core_alloc::fmt::Write;
let mut s = bumpalo::collections::String::new_in(bump); let mut s = bumpalo::collections::String::new_in(bump);
s.write_fmt(args).unwrap(); s.write_fmt(args).unwrap();
s.into_bump_str() (s.into_bump_str(), false)
} }
} }
} }
/// Construct an attribute for an element.
///
/// # Example
///
/// This example creates the `id="my-id"` for some element like `<div
/// id="my-id"/>`.
///
/// ```no_run
/// use dioxus::builder::*;
///
/// let my_id_attr = attr("id", "my-id");
/// ```
pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
Attribute {
name,
value,
namespace: None,
}
}
pub fn virtual_child<'a, T: Properties + 'a>( pub fn virtual_child<'a, T: Properties + 'a>(
cx: NodeFactory<'a>, cx: NodeFactory<'a>,
f: FC<T>, f: FC<T>,
@@ -726,7 +644,7 @@ impl<'a> NodeFactory<'a> {
/// Create some text that's allocated along with the other vnodes /// Create some text that's allocated along with the other vnodes
pub fn text(&self, args: Arguments) -> VNode<'a> { pub fn text(&self, args: Arguments) -> VNode<'a> {
text3(self.bump(), args) VNode::text(self.bump(), args)
} }
/// Create an element builder /// Create an element builder
@@ -749,10 +667,11 @@ impl<'a> NodeFactory<'a> {
val: Arguments, val: Arguments,
namespace: Option<&'static str>, namespace: Option<&'static str>,
) -> Attribute<'a> { ) -> Attribute<'a> {
let value = raw_text(self.bump(), val); let (value, is_static) = raw_text(self.bump(), val);
Attribute { Attribute {
name, name,
value, value,
is_static,
namespace, namespace,
} }
} }
+43 -14
View File
@@ -7,7 +7,7 @@ use crate::{
arena::SharedArena, arena::SharedArena,
events::VirtualEvent, events::VirtualEvent,
innerlude::{Context, Properties, RealDom, RealDomNode, Scope, ScopeIdx, FC}, innerlude::{Context, Properties, RealDom, RealDomNode, Scope, ScopeIdx, FC},
nodebuilder::{text3, NodeFactory}, nodebuilder::NodeFactory,
}; };
use appendlist::AppendList; use appendlist::AppendList;
use bumpalo::Bump; use bumpalo::Bump;
@@ -39,7 +39,7 @@ pub enum VNode<'src> {
/// A "suspended component" /// A "suspended component"
/// This is a masqeurade over an underlying future that needs to complete /// This is a masqeurade over an underlying future that needs to complete
/// When the future is completed, the VNode will then trigger a render /// When the future is completed, the VNode will then trigger a render and the `real` field gets populated
Suspended { real: Cell<RealDomNode> }, Suspended { real: Cell<RealDomNode> },
/// A User-defined componen node (node type COMPONENT_NODE) /// A User-defined componen node (node type COMPONENT_NODE)
@@ -84,6 +84,7 @@ impl<'old, 'new> VNode<'old> {
}, },
namespace: el.namespace.clone(), namespace: el.namespace.clone(),
dom_id: el.dom_id.clone(), dom_id: el.dom_id.clone(),
is_static: el.is_static.clone(),
}; };
VNode::Element(new.alloc_with(move || new_el)) VNode::Element(new.alloc_with(move || new_el))
@@ -121,21 +122,33 @@ impl<'a> VNode<'a> {
children, children,
namespace, namespace,
dom_id: Cell::new(RealDomNode::empty()), dom_id: Cell::new(RealDomNode::empty()),
is_static: Cell::new(false),
}); });
VNode::Element(element) VNode::Element(element)
} }
/// Construct a new text node with the given text. pub fn static_text(text: &'static str) -> VNode {
#[inline]
pub fn text(text: &'a str) -> VNode<'a> {
VNode::Text(VText { VNode::Text(VText {
text, text,
is_static: true,
dom_id: Cell::new(RealDomNode::empty()), dom_id: Cell::new(RealDomNode::empty()),
}) })
} }
/// Construct a new text node with the given text.
pub fn text_args(bump: &'a Bump, args: Arguments) -> VNode<'a> { pub fn text(bump: &'a Bump, args: Arguments) -> VNode<'a> {
text3(bump, args) match args.as_str() {
Some(text) => VNode::static_text(text),
None => {
use bumpalo::core_alloc::fmt::Write;
let mut s = bumpalo::collections::String::new_in(bump);
s.write_fmt(args).unwrap();
VNode::Text(VText {
text: s.into_bump_str(),
is_static: false,
dom_id: Cell::new(RealDomNode::empty()),
})
}
}
} }
#[inline] #[inline]
@@ -191,6 +204,7 @@ impl Debug for VNode<'_> {
#[derive(Clone)] #[derive(Clone)]
pub struct VText<'src> { pub struct VText<'src> {
pub text: &'src str, pub text: &'src str,
pub is_static: bool,
pub dom_id: Cell<RealDomNode>, pub dom_id: Cell<RealDomNode>,
} }
@@ -208,6 +222,7 @@ pub struct VElement<'a> {
pub children: &'a [VNode<'a>], pub children: &'a [VNode<'a>],
pub namespace: Option<&'static str>, pub namespace: Option<&'static str>,
pub dom_id: Cell<RealDomNode>, pub dom_id: Cell<RealDomNode>,
pub is_static: Cell<bool>,
} }
/// An attribute on a DOM node, such as `id="my-thing"` or /// An attribute on a DOM node, such as `id="my-thing"` or
@@ -216,6 +231,7 @@ pub struct VElement<'a> {
pub struct Attribute<'a> { pub struct Attribute<'a> {
pub name: &'static str, pub name: &'static str,
pub value: &'a str, pub value: &'a str,
pub is_static: bool,
/// If an attribute is "namespaced", then it belongs to a group /// If an attribute is "namespaced", then it belongs to a group
/// The most common namespace is the "style" namespace /// The most common namespace is the "style" namespace
@@ -336,6 +352,8 @@ pub struct VComponent<'src> {
pub comparator: Option<&'src dyn Fn(&VComponent) -> bool>, pub comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
pub is_static: bool,
// a pointer into the bump arena (given by the 'src lifetime) // a pointer into the bump arena (given by the 'src lifetime)
// raw_props: Box<dyn Any>, // raw_props: Box<dyn Any>,
raw_props: *const (), raw_props: *const (),
@@ -402,6 +420,15 @@ impl<'a> VComponent<'a> {
None => NodeKey(None), None => NodeKey(None),
}; };
let caller = create_component_caller(component, raw_props);
// If the component does not have children, has no props (we can't memoize props), and has no no key, then we don't
// need to bother diffing it in the future
//
// This is more of an optimization to prevent unnecessary descending through the tree during diffing, rather than
// actually speeding up the diff process itself
let is_static = children.len() == 0 && P::IS_STATIC && key.is_none();
Self { Self {
user_fc, user_fc,
comparator, comparator,
@@ -409,7 +436,8 @@ impl<'a> VComponent<'a> {
children, children,
ass_scope: Cell::new(None), ass_scope: Cell::new(None),
key, key,
caller: create_closure(component, raw_props), caller,
is_static,
mounted_root: Cell::new(RealDomNode::empty()), mounted_root: Cell::new(RealDomNode::empty()),
} }
} }
@@ -417,14 +445,14 @@ impl<'a> VComponent<'a> {
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>; type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
fn create_closure<'a, P: 'a>( pub fn create_component_caller<'a, P: 'a>(
user_component: FC<P>, user_component: FC<P>,
raw_props: *const (), raw_props: *const (),
) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> { ) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
let g: Captured = Rc::new(move |scp: &Scope| -> VNode { let g: Captured = Rc::new(move |scp: &Scope| -> VNode {
// cast back into the right lifetime // cast back into the right lifetime
let safe_props: &'_ P = unsafe { &*(raw_props as *const P) }; let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
let tasks = AppendList::new(); let tasks = RefCell::new(Vec::new());
let cx: Context<P> = Context { let cx: Context<P> = Context {
props: safe_props, props: safe_props,
scope: scp, scope: scp,
@@ -433,11 +461,12 @@ fn create_closure<'a, P: 'a>(
let g = user_component(cx); let g = user_component(cx);
// collect the submitted tasks for task in tasks.borrow_mut().drain(..) {
println!("tasks submittted: {:#?}", tasks.len()); scp.submit_task(task);
// log::debug!("tasks submittted: {:#?}", tasks.len()); }
let g2 = unsafe { std::mem::transmute(g) }; let g2 = unsafe { std::mem::transmute(g) };
g2 g2
}); });
let r: Captured<'static> = unsafe { std::mem::transmute(g) }; let r: Captured<'static> = unsafe { std::mem::transmute(g) };
+42 -28
View File
@@ -2,10 +2,10 @@ use crate::hooklist::HookList;
use crate::{arena::SharedArena, innerlude::*}; use crate::{arena::SharedArena, innerlude::*};
use appendlist::AppendList; use appendlist::AppendList;
use bumpalo::Bump; use bumpalo::Bump;
use futures::FutureExt;
use slotmap::DefaultKey; use slotmap::DefaultKey;
use slotmap::SlotMap; use slotmap::SlotMap;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::sync::Arc;
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
cell::{Cell, RefCell}, cell::{Cell, RefCell},
@@ -14,7 +14,7 @@ use std::{
future::Future, future::Future,
ops::Deref, ops::Deref,
pin::Pin, pin::Pin,
rc::{Rc, Weak}, rc::Rc,
}; };
// We need to pin the hook so it doesn't move as we initialize the list of hooks // We need to pin the hook so it doesn't move as we initialize the list of hooks
@@ -51,7 +51,7 @@ pub struct Scope {
pub event_channel: Rc<dyn Fn() + 'static>, pub event_channel: Rc<dyn Fn() + 'static>,
pub caller: Weak<WrappedCaller>, pub caller: Rc<WrappedCaller>,
// ========================== // ==========================
// slightly unsafe stuff // slightly unsafe stuff
@@ -73,6 +73,8 @@ pub struct Scope {
// NEEDS TO BE PRIVATE // NEEDS TO BE PRIVATE
pub(crate) listeners: RefCell<Vec<(*mut Cell<RealDomNode>, *mut dyn FnMut(VirtualEvent))>>, pub(crate) listeners: RefCell<Vec<(*mut Cell<RealDomNode>, *mut dyn FnMut(VirtualEvent))>>,
pub task_submitter: TaskSubmitter,
pub(crate) suspended_tasks: Vec<*mut Pin<Box<dyn Future<Output = VNode<'static>>>>>, pub(crate) suspended_tasks: Vec<*mut Pin<Box<dyn Future<Output = VNode<'static>>>>>,
} }
@@ -85,13 +87,14 @@ impl Scope {
// Scopes cannot be made anywhere else except for this file // Scopes cannot be made anywhere else except for this file
// Therefore, their lifetimes are connected exclusively to the virtual dom // Therefore, their lifetimes are connected exclusively to the virtual dom
pub fn new<'creator_node>( pub fn new<'creator_node>(
caller: Weak<WrappedCaller>, caller: Rc<WrappedCaller>,
arena_idx: ScopeIdx, arena_idx: ScopeIdx,
parent: Option<ScopeIdx>, parent: Option<ScopeIdx>,
height: u32, height: u32,
event_channel: EventChannel, event_channel: EventChannel,
arena_link: SharedArena, arena_link: SharedArena,
child_nodes: &'creator_node [VNode<'creator_node>], child_nodes: &'creator_node [VNode<'creator_node>],
task_submitter: TaskSubmitter,
) -> Self { ) -> Self {
log::debug!( log::debug!(
"New scope created, height is {}, idx is {:?}", "New scope created, height is {}, idx is {:?}",
@@ -109,6 +112,7 @@ impl Scope {
height, height,
event_channel, event_channel,
arena_link, arena_link,
task_submitter,
listener_idx: Default::default(), listener_idx: Default::default(),
frames: ActiveFrame::new(), frames: ActiveFrame::new(),
hooks: Default::default(), hooks: Default::default(),
@@ -119,7 +123,7 @@ impl Scope {
} }
} }
pub fn update_caller<'creator_node>(&mut self, caller: Weak<WrappedCaller>) { pub fn update_caller<'creator_node>(&mut self, caller: Rc<WrappedCaller>) {
self.caller = caller; self.caller = caller;
} }
@@ -136,19 +140,15 @@ impl Scope {
// This breaks any latent references, invalidating every pointer referencing into it. // This breaks any latent references, invalidating every pointer referencing into it.
self.frames.next().bump.reset(); self.frames.next().bump.reset();
log::debug!("clearing listeners!");
// Remove all the outdated listeners // Remove all the outdated listeners
self.listeners.borrow_mut().clear(); self.listeners.borrow_mut().clear();
unsafe { self.hooks.reset() }; unsafe { self.hooks.reset() };
self.listener_idx.set(0); self.listener_idx.set(0);
let caller = self
.caller
.upgrade()
.ok_or(Error::FatalInternal("Failed to get caller"))?;
// Cast the caller ptr from static to one with our own reference // Cast the caller ptr from static to one with our own reference
let c3: &WrappedCaller = caller.as_ref(); let c3: &WrappedCaller = self.caller.as_ref();
self.frames.cur_frame_mut().head_node = unsafe { self.call_user_component(c3) }; self.frames.cur_frame_mut().head_node = unsafe { self.call_user_component(c3) };
@@ -176,28 +176,42 @@ impl Scope {
// Convert the raw ptr into an actual object // Convert the raw ptr into an actual object
// This operation is assumed to be safe // This operation is assumed to be safe
log::debug!("Calling listeners! {:?}", self.listeners.borrow().len()); log::debug!(
let mut listners = self.listeners.borrow_mut(); "There are {:?} listeners associated with this scope {:#?}",
let (_, listener) = listners self.listeners.borrow().len(),
.iter() self.arena_idx
.find(|(domptr, _)| { );
let p = unsafe { &**domptr };
p.get() == real_node_id.expect("realdomnode not found, propery handling of true virtual events not managed")
})
.expect(&format!(
"Failed to find real node with ID {:?}",
real_node_id
));
// TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster! let mut listners = self.listeners.borrow_mut();
unsafe {
let mut listener_fn = &mut **listener; // let listener = listners.get(trigger);
listener_fn(event); let raw_listener = listners.iter().find(|(domptr, _)| {
let search = unsafe { &**domptr };
let search_id = search.get();
log::info!("searching listener {:#?}", search_id);
match real_node_id {
Some(e) => search_id == e,
None => false,
}
});
match raw_listener {
Some((_node, listener)) => unsafe {
// TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster!
let listener_fn = &mut **listener;
listener_fn(event);
},
None => todo!(),
} }
Ok(()) Ok(())
} }
pub fn submit_task(&self, task: &mut Pin<Box<dyn Future<Output = ()>>>) {
log::debug!("Task submitted into scope");
(self.task_submitter)(DTask::new(task, self.arena_idx));
}
pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
self.frames.current_head_node() self.frames.current_head_node()
} }
@@ -210,7 +224,7 @@ impl Scope {
self.frames.cur_frame() self.frames.cur_frame()
} }
pub(crate) fn root<'a>(&'a self) -> &'a VNode<'a> { pub fn root<'a>(&'a self) -> &'a VNode<'a> {
&self.frames.cur_frame().head_node &self.frames.cur_frame().head_node
} }
} }
+1 -4
View File
@@ -12,10 +12,7 @@
//! //!
//! //!
use crate::{ use crate::{innerlude::Attribute, nodebuilder::NodeFactory};
innerlude::Attribute,
nodebuilder::{text3, NodeFactory},
};
pub struct StyleBuilder; pub struct StyleBuilder;
pub trait AsAttr<'a> { pub trait AsAttr<'a> {
+44 -36
View File
@@ -16,14 +16,16 @@ use std::{
task::{Context, Poll}, task::{Context, Poll},
}; };
use futures::{Future, Stream, StreamExt}; use futures_util::{Future, Stream, StreamExt};
use slotmap::{DefaultKey, SlotMap}; use slotmap::{DefaultKey, SlotMap};
use crate::{events::EventTrigger, prelude::ScopeIdx}; use crate::{events::EventTrigger, prelude::ScopeIdx};
pub type TaskSubmitter = Arc<dyn Fn(DTask)>;
pub struct TaskQueue { pub struct TaskQueue {
slots: Arc<RwLock<SlotMap<DefaultKey, DTask>>>, slots: Arc<RwLock<SlotMap<DefaultKey, DTask>>>,
submitter: Arc<dyn Fn(DTask)>, submitter: TaskSubmitter,
} }
impl TaskQueue { impl TaskQueue {
@@ -34,11 +36,16 @@ impl TaskQueue {
let submitter = Arc::new(move |task| { let submitter = Arc::new(move |task| {
let mut slots = slots2.write().unwrap(); let mut slots = slots2.write().unwrap();
log::debug!("Task submitted into global task queue");
slots.insert(task); slots.insert(task);
}); });
Self { slots, submitter } Self { slots, submitter }
} }
pub fn new_submitter(&self) -> TaskSubmitter {
self.submitter.clone()
}
pub fn submit_task(&mut self, task: DTask) -> TaskHandle { pub fn submit_task(&mut self, task: DTask) -> TaskHandle {
let key = self.slots.write().unwrap().insert(task); let key = self.slots.write().unwrap().insert(task);
TaskHandle { key } TaskHandle { key }
@@ -126,43 +133,44 @@ impl DTask {
} }
} }
mod tests { // #[cfg(test)]
use std::time::Duration; // mod tests {
// use std::time::Duration;
use super::*; // use super::*;
use bumpalo::Bump; // use bumpalo::Bump;
#[async_std::test] // #[async_std::test]
async fn example() { // async fn example() {
let bump = Bump::new(); // let bump = Bump::new();
type RawTask = Pin<Box<dyn Future<Output = ()>>>; // type RawTask = Pin<Box<dyn Future<Output = ()>>>;
// build the three // // build the three
let f1 = bump.alloc(Box::pin(async { // let f1 = bump.alloc(Box::pin(async {
// // //
async_std::task::sleep(Duration::from_secs(3)).await; // async_std::task::sleep(Duration::from_secs(3)).await;
println!("3 sec") // println!("3 sec")
}) as RawTask); // }) as RawTask);
let f2 = bump.alloc(Box::pin(async { // let f2 = bump.alloc(Box::pin(async {
// // //
async_std::task::sleep(Duration::from_secs(2)).await; // async_std::task::sleep(Duration::from_secs(2)).await;
println!("2 sec") // println!("2 sec")
}) as RawTask); // }) as RawTask);
let f3 = bump.alloc(Box::pin(async { // let f3 = bump.alloc(Box::pin(async {
// // //
async_std::task::sleep(Duration::from_secs(1)).await; // async_std::task::sleep(Duration::from_secs(1)).await;
println!("1 sec"); // println!("1 sec");
}) as RawTask); // }) as RawTask);
let mut queue = TaskQueue::new(); // let mut queue = TaskQueue::new();
queue.submit_task(DTask::debug_new(f1)); // queue.submit_task(DTask::debug_new(f1));
queue.submit_task(DTask::debug_new(f2)); // queue.submit_task(DTask::debug_new(f2));
queue.submit_task(DTask::debug_new(f3)); // queue.submit_task(DTask::debug_new(f3));
while !queue.is_empty() { // while !queue.is_empty() {
let next = queue.next().await; // let next = queue.next().await;
println!("Event received {:#?}", next); // println!("Event received {:#?}", next);
} // }
} // }
} // }
+73 -3
View File
@@ -4,6 +4,8 @@ use std::{
vec::Drain, vec::Drain,
}; };
use futures_util::StreamExt;
use crate::innerlude::*; use crate::innerlude::*;
#[derive(PartialEq, Debug, Clone, Default)] #[derive(PartialEq, Debug, Clone, Default)]
@@ -67,18 +69,28 @@ impl RealDomNode {
pub struct DebugDom { pub struct DebugDom {
counter: u64, counter: u64,
logging: bool,
} }
impl DebugDom { impl DebugDom {
pub fn new() -> Self { pub fn new() -> Self {
Self { counter: 0 } Self {
counter: 0,
logging: false,
}
}
pub fn with_logging_enabled() -> Self {
Self {
counter: 0,
logging: true,
}
} }
} }
impl<'a> RealDom<'a> for DebugDom { impl<'a> RealDom<'a> for DebugDom {
fn push_root(&mut self, root: RealDomNode) {} fn push_root(&mut self, root: RealDomNode) {}
fn append_child(&mut self) {} fn append_children(&mut self, many: u32) {}
fn replace_with(&mut self) {} fn replace_with(&mut self, many: u32) {}
fn remove(&mut self) {} fn remove(&mut self) {}
@@ -120,3 +132,61 @@ impl<'a> RealDom<'a> for DebugDom {
todo!() todo!()
} }
} }
async fn launch_demo(app: FC<()>) {
let mut dom = VirtualDom::new(app);
let mut real_dom = DebugDom::new();
dom.rebuild(&mut real_dom).unwrap();
while let Some(evt) = dom.tasks.next().await {
//
log::debug!("Event triggered! {:#?}", evt);
}
}
// #[cfg(test)]
// mod tests {
// use super::*;
// use crate as dioxus;
// use std::{pin::Pin, time::Duration};
// use crate::builder::DioxusElement;
// use dioxus::prelude::*;
// use futures::Future;
// #[async_std::test]
// async fn async_tick() {
// static App: FC<()> = |cx| {
// // let mut count = use_state(cx, || 0);
// let mut fut = cx.use_hook(
// move || {
// Box::pin(async {
// //
// let mut tick: i32 = 0;
// loop {
// async_std::task::sleep(Duration::from_millis(250)).await;
// log::debug!("ticking forward... {}", tick);
// tick += 1;
// // match surf::get(ENDPOINT).recv_json::<DogApi>().await {
// // Ok(_) => (),
// // Err(_) => (),
// // }
// }
// }) as Pin<Box<dyn Future<Output = ()> + 'static>>
// },
// |h| h,
// |_| {},
// );
// cx.submit_task(fut);
// cx.render(LazyNodes::new(move |f| {
// f.text(format_args!("it's sorta working"))
// }))
// };
// std::env::set_var("RUST_LOG", "debug");
// env_logger::init();
// launch_demo(App).await;
// }
// }
+48 -38
View File
@@ -24,6 +24,9 @@ use crate::{arena::SharedArena, innerlude::*};
use appendlist::AppendList; use appendlist::AppendList;
use slotmap::DefaultKey; use slotmap::DefaultKey;
use slotmap::SlotMap; use slotmap::SlotMap;
use std::any::Any;
use std::cell::RefCell;
use std::pin::Pin;
use std::{any::TypeId, fmt::Debug, rc::Rc}; use std::{any::TypeId, fmt::Debug, rc::Rc};
pub type ScopeIdx = DefaultKey; pub type ScopeIdx = DefaultKey;
@@ -45,11 +48,9 @@ pub struct VirtualDom {
/// All components dump their updates into a queue to be processed /// All components dump their updates into a queue to be processed
pub(crate) event_queue: EventQueue, pub(crate) event_queue: EventQueue,
pub(crate) tasks: TaskQueue, pub tasks: TaskQueue,
/// a strong allocation to the "caller" for the original component and its props root_props: std::pin::Pin<Box<dyn std::any::Any>>,
#[doc(hidden)]
_root_caller: Rc<WrappedCaller>,
/// Type of the original cx. This is stored as TypeId so VirtualDom does not need to be generic. /// Type of the original cx. This is stored as TypeId so VirtualDom does not need to be generic.
/// ///
@@ -129,59 +130,36 @@ impl VirtualDom {
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self { pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
let components = SharedArena::new(SlotMap::new()); let components = SharedArena::new(SlotMap::new());
// Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
// Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope. let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
let _root_caller: Rc<WrappedCaller> = Rc::new(move |scope: &Scope| {
// let _root_caller: Rc<OpaqueComponent<'static>> = Rc::new(move |scope| {
// the lifetime of this closure is just as long as the lifetime on the scope reference
// this closure moves root props (which is static) into this closure
let props = unsafe { &*(&root_props as *const _) };
let tasks = AppendList::new();
let t2 = &tasks;
let cx = Context {
props,
scope,
tasks: t2,
};
let nodes = root(cx);
log::debug!("There were {:?} tasks submitted", tasks.len());
// cast a weird lifetime to shake the appendlist thing
// TODO: move all of this into the same logic that governs other components
// we want to wrap everything in a dioxus root component
unsafe { std::mem::transmute(nodes) }
// std::mem::drop(tasks);
//
// nodes
});
// Create a weak reference to the OpaqueComponent for the root scope to use as its render function
let caller_ref = Rc::downgrade(&_root_caller);
// Build a funnel for hooks to send their updates into. The `use_hook` method will call into the update funnel. // Build a funnel for hooks to send their updates into. The `use_hook` method will call into the update funnel.
let event_queue = EventQueue::default(); let event_queue = EventQueue::default();
let _event_queue = event_queue.clone(); let _event_queue = event_queue.clone();
// Make the first scope
// We don't run the component though, so renderers will need to call "rebuild" when they initialize their DOM
let link = components.clone(); let link = components.clone();
let tasks = TaskQueue::new();
let submitter = tasks.new_submitter();
let base_scope = components let base_scope = components
.with(|arena| { .with(|arena| {
arena.insert_with_key(move |myidx| { arena.insert_with_key(move |myidx| {
let event_channel = _event_queue.new_channel(0, myidx); let event_channel = _event_queue.new_channel(0, myidx);
Scope::new(caller_ref, myidx, None, 0, event_channel, link, &[]) let caller = crate::nodes::create_component_caller(root, props_ptr as *const _);
Scope::new(caller, myidx, None, 0, event_channel, link, &[], submitter)
}) })
}) })
.unwrap(); .unwrap();
log::debug!("base scope is {:#?}", base_scope);
Self { Self {
_root_caller,
base_scope, base_scope,
event_queue, event_queue,
components, components,
tasks: TaskQueue::new(), root_props,
tasks,
_root_prop_type: TypeId::of::<P>(), _root_prop_type: TypeId::of::<P>(),
} }
} }
@@ -191,6 +169,38 @@ impl VirtualDom {
// Private Methods for the VirtualDom // Private Methods for the VirtualDom
// ====================================== // ======================================
impl VirtualDom { impl VirtualDom {
/// Rebuilds the VirtualDOM from scratch, but uses a "dummy" RealDom.
///
/// Used in contexts where a real copy of the structure doesn't matter, and the VirtualDOM is the source of truth.
///
/// ## Why?
///
/// This method uses the `DebugDom` under the hood - essentially making the VirtualDOM's diffing patches a "no-op".
///
/// SSR takes advantage of this by using Dioxus itself as the source of truth, and rendering from the tree directly.
pub fn rebuild_in_place(&mut self) -> Result<()> {
let mut realdom = DebugDom::new();
let mut diff_machine = DiffMachine::new(
&mut realdom,
&self.components,
self.base_scope,
self.event_queue.clone(),
&self.tasks,
);
// Schedule an update and then immediately call it on the root component
// This is akin to a hook being called from a listener and requring a re-render
// Instead, this is done on top-level component
let base = self.components.try_get(self.base_scope)?;
let update = &base.event_channel;
update();
self.progress_completely(&mut diff_machine)?;
Ok(())
}
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
/// Currently this doesn't do what we want it to do /// Currently this doesn't do what we want it to do
pub fn rebuild<'s, Dom: RealDom<'s>>(&'s mut self, realdom: &mut Dom) -> Result<()> { pub fn rebuild<'s, Dom: RealDom<'s>>(&'s mut self, realdom: &mut Dom) -> Result<()> {
+1 -1
View File
@@ -7,4 +7,4 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
dioxus-core = { path = "../core" } dioxus-core = { path="../core" }
+8
View File
@@ -24,6 +24,14 @@ use std::{
/// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations /// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations
/// will automatically be called on the WIP value. /// will automatically be called on the WIP value.
/// ///
/// ## Combinators
///
/// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality:
/// - `.classic()` and `.split()` convert the hook into the classic React-style hook
/// ```rust
/// let (state, set_state) = use_state(cx, || 10).split()
/// ```
///
/// ///
/// Usage: /// Usage:
/// ```ignore /// ```ignore
+1
View File
@@ -11,6 +11,7 @@ dioxus-core = { path = "../core", version = "0.1.0", features = ["serialize"] }
[dev-dependencies] [dev-dependencies]
dioxus-html = { path = "../html" }
tide-websockets = "*" tide-websockets = "*"
thiserror = "1.0.23" thiserror = "1.0.23"
log = "0.4.13" log = "0.4.13"
+18
View File
@@ -0,0 +1,18 @@
# Dioxus SSR
Render a Dioxus VirtualDOM to a string.
```rust
// Our app:
const App: FC<()> = |cx| cx.render(rsx!(div {"hello world!"}));
// Build the virtualdom from our app
let mut vdom = VirtualDOM::new(App);
// This runs components, lifecycles, etc. without needing a physical dom. Some features (like noderef) won't work.
vdom.rebuild_in_place();
// Render the entire virtualdom from the root
let text = dioxus_ssr::render_root(&vdom);
assert_eq!(text, "<div>hello world!</div>")
```
+2 -101
View File
@@ -1,102 +1,3 @@
<div title="About W3Schools"> <div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools"> hello world!
<p title="About W3Schools">
Hello world!: 0
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 1
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 2
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 3
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 4
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 5
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 6
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 7
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 8
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 9
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 10
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 11
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 12
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 13
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 14
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 15
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 16
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 17
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 18
</p>
</div>
<div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
<p title="About W3Schools">
Hello world!: 19
</p>
</div>
</div> </div>
+103 -54
View File
@@ -7,79 +7,128 @@
//! an external renderer is needed to progress the component lifecycles. The `TextRenderer` shows how to use the Virtual DOM //! an external renderer is needed to progress the component lifecycles. The `TextRenderer` shows how to use the Virtual DOM
//! API to progress these lifecycle events to generate a fully-mounted Virtual DOM instance which can be renderer in the //! API to progress these lifecycle events to generate a fully-mounted Virtual DOM instance which can be renderer in the
//! `render` method. //! `render` method.
//!
//! ```ignore
//! fn main() {
//! let renderer = TextRenderer::<()>::new(|_| html! {<div> "Hello world" </div>});
//! let output = renderer.render();
//! assert_eq!(output, "<div>Hello World</div>");
//! }
//! ```
//!
//! The `TextRenderer` is particularly useful when needing to cache a Virtual DOM in between requests
//!
use dioxus_core::prelude::{VNode, FC}; use std::fmt::{Arguments, Display, Formatter};
pub mod tostring;
pub mod prelude { use dioxus_core::prelude::*;
pub use dioxus_core::prelude::*; use dioxus_core::{nodes::VNode, prelude::ScopeIdx, virtual_dom::VirtualDom};
use std::io::{BufWriter, Result, Write};
pub fn render_root(vdom: &VirtualDom) -> String {
format!("{:}", TextRenderer::new(vdom))
} }
/// The `TextRenderer` provides a way of rendering a Dioxus Virtual DOM to a String. /// A configurable text renderer for the Dioxus VirtualDOM.
///
///
/// ## Details
///
/// This uses the `Formatter` infrastructure so you can write into anything that supports `write_fmt`. We can't accept
/// any generic writer, so you need to "Display" the text renderer. This is done through `format!` or `format_args!`
/// ///
/// ///
/// ///
pub struct TextRenderer<T> { ///
_root_type: std::marker::PhantomData<T>, pub struct TextRenderer<'a> {
vdom: &'a VirtualDom,
} }
impl<T> TextRenderer<T> { impl<'a> TextRenderer<'a> {
/// Create a new text-renderer instance from a functional component root. fn new(vdom: &'a VirtualDom) -> Self {
/// Automatically progresses the creation of the VNode tree to completion. Self { vdom }
/// }
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
pub fn new(root: FC<T>) -> Self { fn html_render(&self, node: &VNode, f: &mut std::fmt::Formatter) -> std::fmt::Result {
Self { match node {
_root_type: std::marker::PhantomData {}, VNode::Element(el) => {
write!(f, "<{}", el.tag_name)?;
for attr in el.attributes {
write!(f, " {}=\"{}\"", attr.name, attr.value)?;
}
write!(f, ">\n")?;
for child in el.children {
self.html_render(child, f)?;
}
write!(f, "\n</{}>", el.tag_name)?;
Ok(())
}
VNode::Text(text) => write!(f, "{}", text.text),
VNode::Suspended { .. } => todo!(),
VNode::Component(vcomp) => {
todo!()
// let id = vcomp.ass_scope.borrow().unwrap();
// let id = vcomp.ass_scope.as_ref().borrow().unwrap();
// let new_node = dom
// .components
// .try_get(id)
// .unwrap()
// .frames
// .current_head_node();
// html_render(&dom, new_node, f)
}
VNode::Fragment(_) => todo!(),
} }
} }
}
/// Create a new text renderer from an existing Virtual DOM. impl Display for TextRenderer<'_> {
/// This will progress the existing VDom's events to completion. fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
pub fn from_vdom() -> Self { let root = self.vdom.base_scope();
todo!() let root_node = root.root();
self.html_render(root_node, f)
} }
}
/// Pass new args to the root function #[cfg(test)]
pub fn update(&mut self, new_val: T) { mod tests {
todo!() use super::*;
}
use dioxus_core as dioxus;
use dioxus_html as dioxus_elements;
const SIMPLE_APP: FC<()> = |cx| {
//
cx.render(rsx!(div {
"hello world!"
}))
};
const SLIGHTLY_MORE_COMPLEX: FC<()> = |cx| {
cx.render(rsx! {
div {
title: "About W3Schools"
{(0..20).map(|f| rsx!{
div {
title: "About W3Schools"
style: "color:blue;text-align:center"
class: "About W3Schools"
p {
title: "About W3Schools"
"Hello world!: {f}"
}
}
})}
}
})
};
/// Modify the root function in place, forcing a re-render regardless if the props changed #[test]
pub fn update_mut(&mut self, modifier: impl Fn(&mut T)) { fn test_to_string_works() {
todo!() let mut dom = VirtualDom::new(SIMPLE_APP);
dom.rebuild_in_place().expect("failed to run virtualdom");
dbg!(render_root(&dom));
} }
/// Immediately render a VNode to string #[test]
pub fn to_text(root: VNode) -> String { fn test_write_to_file() {
todo!() use std::fs::File;
}
/// Render the virtual DOM to a string let mut file = File::create("index.html").unwrap();
pub fn render(&self) -> String {
let mut buffer = String::new();
// iterate through the internal patch queue of virtual dom, and apply them to the buffer let mut dom = VirtualDom::new(SIMPLE_APP);
/* dom.rebuild_in_place().expect("failed to run virtualdom");
*/
todo!()
}
/// Render VDom to an existing buffer file.write_fmt(format_args!("{}", TextRenderer::new(&dom)))
/// TODO @Jon, support non-string buffers to actually make this useful .unwrap();
/// Currently, this only supports overwriting an existing buffer, instead of just
pub fn render_mut(&self, buf: &mut String) {
todo!()
} }
} }
-89
View File
@@ -1,89 +0,0 @@
use std::fmt::Display;
use dioxus_core::prelude::*;
use dioxus_core::{nodes::VNode, prelude::ScopeIdx, virtual_dom::VirtualDom};
struct SsrRenderer {
dom: VirtualDom,
}
impl Display for SsrRenderer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let node = self.dom.base_scope().frames.current_head_node();
html_render(&self.dom, node, f)
}
}
// recursively walk the tree
fn html_render(
dom: &VirtualDom,
node: &VNode,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
match node {
VNode::Element(el) => {
write!(f, "<{}", el.tag_name)?;
for attr in el.attributes {
write!(f, " {}=\"{}\"", attr.name, attr.value)?;
}
write!(f, ">\n")?;
for child in el.children {
html_render(dom, child, f)?;
}
write!(f, "\n</{}>", el.tag_name)?;
Ok(())
}
VNode::Text(text) => write!(f, "{}", text),
VNode::Suspended => todo!(),
VNode::Component(vcomp) => {
let id = vcomp.ass_scope.borrow().unwrap();
// let id = vcomp.ass_scope.as_ref().borrow().unwrap();
let new_node = dom
.components
.try_get(id)
.unwrap()
.frames
.current_head_node();
html_render(&dom, new_node, f)
}
VNode::Fragment(_) => todo!(),
}
}
#[test]
fn test_serialize() {
let mut dom = VirtualDom::new(|cx| {
//
//
//
cx.render(rsx! {
div {
title: "About W3Schools"
{(0..20).map(|f| rsx!{
div {
title: "About W3Schools"
// style: "color:blue;text-align:center"
class: "About W3Schools"
p {
title: "About W3Schools"
"Hello world!: {f}"
}
}
})}
}
})
});
dom.rebuild();
let renderer = SsrRenderer { dom };
use std::fs::File;
use std::io::prelude::*;
let mut file = File::create("index.html").unwrap();
let buf = renderer.to_string();
// dbg!(buf);
file.write(buf.as_bytes());
// dbg!(renderer.to_string());
}
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"rust-analyzer.inlayHints.enable": false "rust-analyzer.inlayHints.enable": false
} }
+6 -2
View File
@@ -23,11 +23,12 @@ generational-arena = "0.2.8"
wasm-bindgen-test = "0.3.21" wasm-bindgen-test = "0.3.21"
once_cell = "1.7.2" once_cell = "1.7.2"
atoms = { path="../atoms" } atoms = { path="../atoms" }
async-channel = "1.6.1" async-channel = "1.6.1"
anyhow = "1.0.41" anyhow = "1.0.41"
slotmap = "1.0.3" slotmap = "1.0.3"
futures-util = "0.3.15"
[dependencies.web-sys] [dependencies.web-sys]
version = "0.3.50" version = "0.3.50"
features = [ features = [
@@ -72,6 +73,9 @@ crate-type = ["cdylib", "rlib"]
[dev-dependencies] [dev-dependencies]
im-rc = "15.0.0" im-rc = "15.0.0"
rand = { version="0.8.4", features=["small_rng"] } # rand = { version="0.8.4", features=["small_rng"] }
separator = "0.4.1" separator = "0.4.1"
uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] } uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] }
dioxus-hooks = { path="../hooks" }
gloo-timers = { version="0.2.1", features=["futures"] }
# wasm-timer = "0.2.5"
+71
View File
@@ -0,0 +1,71 @@
//! Basic example that renders a simple VNode to the browser.
use dioxus::events::on::MouseEvent;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_hooks::*;
use dioxus_html as dioxus_elements;
// use wasm_timer;
use std::future::Future;
use std::{pin::Pin, time::Duration};
use dioxus::prelude::*;
use dioxus_web::*;
fn main() {
// Setup logging
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
static App: FC<()> = |cx| {
// let mut count = use_state(cx, || 0);
let fut = cx.use_hook(
move || {
Box::pin(async {
let mut tick: i32 = 0;
log::debug!("yeet!");
loop {
gloo_timers::future::TimeoutFuture::new(250).await;
log::debug!("ticking forward... {}", tick);
tick += 1;
}
}) as Pin<Box<dyn Future<Output = ()> + 'static>>
},
|h| h,
|_| {},
);
cx.submit_task(fut);
let state = use_state(cx, || 0);
cx.render(rsx! {
div {
section { class: "py-12 px-4 text-center"
div { class: "w-full max-w-2xl mx-auto"
span { class: "text-sm font-semibold"
"count: {state}"
}
div {
button {
onclick: move |_| state.set(state + 1)
"incr"
}
br {}
br {}
button {
onclick: move |_| state.set(state - 1)
"decr"
}
}
}
}
}
})
};
+52
View File
@@ -0,0 +1,52 @@
//! Basic example that renders a simple VNode to the browser.
use dioxus::events::on::MouseEvent;
use dioxus_core as dioxus;
use dioxus_core::prelude::*;
use dioxus_hooks::*;
use dioxus_html as dioxus_elements;
// use wasm_timer;
use std::future::Future;
use std::{pin::Pin, time::Duration};
use dioxus::prelude::*;
use dioxus_web::*;
fn main() {
// Setup logging
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once();
// Run the app
wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
}
static App: FC<()> = |cx| {
let mut state = use_state(cx, || 0);
cx.render(rsx! {
div {
section { class: "py-12 px-4 text-center"
div { class: "w-full max-w-2xl mx-auto"
span { class: "text-sm font-semibold"
"static subtree"
}
}
}
section { class: "py-12 px-4 text-center"
div { class: "w-full max-w-2xl mx-auto"
span { class: "text-sm font-semibold"
"dynamic subtree {state}"
}
div {
button { onclick: move |_| state+=1, "incr" }
br {}
button { onclick: move |_| state-=1, "decr" }
}
}
}
}
})
};
+33 -2
View File
@@ -3,6 +3,8 @@
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys. //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
use dioxus::prelude::{Context, Properties, VNode}; use dioxus::prelude::{Context, Properties, VNode};
use futures_util::{pin_mut, Stream, StreamExt};
use fxhash::FxHashMap; use fxhash::FxHashMap;
use web_sys::{window, Document, Element, Event, Node}; use web_sys::{window, Document, Element, Event, Node};
// use futures::{channel::mpsc, SinkExt, StreamExt}; // use futures::{channel::mpsc, SinkExt, StreamExt};
@@ -70,12 +72,41 @@ impl WebsysRenderer {
self.internal_dom.rebuild(&mut websys_dom)?; self.internal_dom.rebuild(&mut websys_dom)?;
while let Some(trigger) = websys_dom.wait_for_event().await { log::info!("Going into event loop");
loop {
let trigger = {
let real_queue = websys_dom.wait_for_event();
let task_queue = (&mut self.internal_dom.tasks).next();
pin_mut!(real_queue);
pin_mut!(task_queue);
match futures_util::future::select(real_queue, task_queue).await {
futures_util::future::Either::Left((trigger, _)) => trigger,
futures_util::future::Either::Right((trigger, _)) => trigger,
}
};
log::info!("event received");
let root_node = body_element.first_child().unwrap(); let root_node = body_element.first_child().unwrap();
websys_dom.stack.push(root_node.clone()); websys_dom.stack.push(root_node.clone());
self.internal_dom self.internal_dom
.progress_with_event(&mut websys_dom, trigger)?; .progress_with_event(&mut websys_dom, trigger.unwrap())?;
// let t2 = self.internal_dom.tasks.next();
// futures::select! {
// trigger = t1 => {
// log::info!("event received");
// let root_node = body_element.first_child().unwrap();
// websys_dom.stack.push(root_node.clone());
// self.internal_dom
// .progress_with_event(&mut websys_dom, trigger)?;
// },
// () = t2 => {}
// };
} }
// while let Some(trigger) = websys_dom.wait_for_event().await {
// }
Ok(()) // should actually never return from this, should be an error, rustc just cant see it Ok(()) // should actually never return from this, should be an error, rustc just cant see it
} }
+14 -11
View File
@@ -78,7 +78,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
self.stack.push(domnode.clone()); self.stack.push(domnode.clone());
} }
fn append_child(&mut self) { fn append_children(&mut self, many: u32) {
log::debug!("Called [`append_child`]"); log::debug!("Called [`append_child`]");
let child = self.stack.pop(); let child = self.stack.pop();
@@ -99,7 +99,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
self.stack.top().append_child(&child).unwrap(); self.stack.top().append_child(&child).unwrap();
} }
fn replace_with(&mut self) { fn replace_with(&mut self, many: u32) {
log::debug!("Called [`replace_with`]"); log::debug!("Called [`replace_with`]");
let new_node = self.stack.pop(); let new_node = self.stack.pop();
let old_node = self.stack.pop(); let old_node = self.stack.pop();
@@ -193,15 +193,16 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
&mut self, &mut self,
event: &'static str, event: &'static str,
scope: dioxus_core::prelude::ScopeIdx, scope: dioxus_core::prelude::ScopeIdx,
el_id: usize, _element_id: usize,
real_id: RealDomNode, real_id: RealDomNode,
) { ) {
let (_on, event) = event.split_at(2);
let event = wasm_bindgen::intern(event); let event = wasm_bindgen::intern(event);
log::debug!( log::debug!(
"Called [`new_event_listener`]: {}, {:?}, {}, {:?}", "Called [`new_event_listener`]: {}, {:?}, {}, {:?}",
event, event,
scope, scope,
el_id, _element_id,
real_id real_id
); );
// attach the correct attributes to the element // attach the correct attributes to the element
@@ -215,10 +216,11 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
.dyn_ref::<Element>() .dyn_ref::<Element>()
.expect(&format!("not an element: {:?}", el)); .expect(&format!("not an element: {:?}", el));
let gi_id = scope.data().as_ffi(); let scope_id = scope.data().as_ffi();
el.set_attribute( el.set_attribute(
&format!("dioxus-event-{}", event), &format!("dioxus-event-{}", event),
&format!("{}.{}.{}", gi_id, el_id, real_id.0), &format!("{}.{}", scope_id, real_id.0),
// &format!("{}.{}.{}", gi_id, el_id, real_id.0),
) )
.unwrap(); .unwrap();
@@ -566,19 +568,20 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
.next() .next()
.and_then(|f| f.parse::<u64>().ok()) .and_then(|f| f.parse::<u64>().ok())
.context("failed to parse gi id")?; .context("failed to parse gi id")?;
let el_id = fields // let el_id = fields
.next() // .next()
.and_then(|f| f.parse::<usize>().ok()) // .and_then(|f| f.parse::<usize>().ok())
.context("failed to parse el id")?; // .context("failed to parse el id")?;
let real_id = fields let real_id = fields
.next() .next()
.and_then(|f| f.parse::<u64>().ok().map(RealDomNode::new)) .and_then(|f| f.parse::<u64>().ok().map(RealDomNode::new))
.context("failed to parse real id")?; .context("failed to parse real id")?;
// Call the trigger // Call the trigger
log::debug!("decoded gi_id: {}, li_idx: {}", gi_id, el_id); log::debug!("decoded scope_id: {}, node_id: {:#?}", gi_id, real_id);
let triggered_scope: ScopeIdx = KeyData::from_ffi(gi_id).into(); let triggered_scope: ScopeIdx = KeyData::from_ffi(gi_id).into();
log::debug!("Triggered scope is {:#?}", triggered_scope);
Ok(EventTrigger::new( Ok(EventTrigger::new(
virtual_event_from_websys_event(event), virtual_event_from_websys_event(event),
triggered_scope, triggered_scope,
+2 -2
View File
@@ -37,11 +37,11 @@ impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
self.edits.push(PushRoot { root: root.0 }); self.edits.push(PushRoot { root: root.0 });
} }
fn append_child(&mut self) { fn append_children(&mut self, many: u32) {
self.edits.push(AppendChild); self.edits.push(AppendChild);
} }
fn replace_with(&mut self) { fn replace_with(&mut self, many: u32) {
self.edits.push(ReplaceWith); self.edits.push(ReplaceWith);
} }