Fn(&'r Scope) -> VNode<'r> + 'a>;
-fn create_closure<'a, P: 'a>(
+pub fn create_component_caller<'a, P: 'a>(
user_component: FC,
raw_props: *const (),
) -> Rc Fn(&'r Scope) -> VNode<'r>> {
let g: Captured = Rc::new(move |scp: &Scope| -> VNode {
// cast back into the right lifetime
let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
- let tasks = AppendList::new();
+ let tasks = RefCell::new(Vec::new());
let cx: Context = Context {
props: safe_props,
scope: scp,
@@ -433,11 +461,12 @@ fn create_closure<'a, P: 'a>(
let g = user_component(cx);
- // collect the submitted tasks
- println!("tasks submittted: {:#?}", tasks.len());
- // log::debug!("tasks submittted: {:#?}", tasks.len());
+ for task in tasks.borrow_mut().drain(..) {
+ scp.submit_task(task);
+ }
let g2 = unsafe { std::mem::transmute(g) };
+
g2
});
let r: Captured<'static> = unsafe { std::mem::transmute(g) };
diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs
index c022ea09d..5d0783a90 100644
--- a/packages/core/src/scope.rs
+++ b/packages/core/src/scope.rs
@@ -2,10 +2,10 @@ use crate::hooklist::HookList;
use crate::{arena::SharedArena, innerlude::*};
use appendlist::AppendList;
use bumpalo::Bump;
-use futures::FutureExt;
use slotmap::DefaultKey;
use slotmap::SlotMap;
use std::marker::PhantomData;
+use std::sync::Arc;
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell},
@@ -14,7 +14,7 @@ use std::{
future::Future,
ops::Deref,
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
@@ -51,7 +51,7 @@ pub struct Scope {
pub event_channel: Rc,
- pub caller: Weak,
+ pub caller: Rc,
// ==========================
// slightly unsafe stuff
@@ -73,6 +73,8 @@ pub struct Scope {
// NEEDS TO BE PRIVATE
pub(crate) listeners: RefCell, *mut dyn FnMut(VirtualEvent))>>,
+ pub task_submitter: TaskSubmitter,
+
pub(crate) suspended_tasks: Vec<*mut Pin>>>>,
}
@@ -85,13 +87,14 @@ impl Scope {
// Scopes cannot be made anywhere else except for this file
// Therefore, their lifetimes are connected exclusively to the virtual dom
pub fn new<'creator_node>(
- caller: Weak,
+ caller: Rc,
arena_idx: ScopeIdx,
parent: Option,
height: u32,
event_channel: EventChannel,
arena_link: SharedArena,
child_nodes: &'creator_node [VNode<'creator_node>],
+ task_submitter: TaskSubmitter,
) -> Self {
log::debug!(
"New scope created, height is {}, idx is {:?}",
@@ -109,6 +112,7 @@ impl Scope {
height,
event_channel,
arena_link,
+ task_submitter,
listener_idx: Default::default(),
frames: ActiveFrame::new(),
hooks: Default::default(),
@@ -119,7 +123,7 @@ impl Scope {
}
}
- pub fn update_caller<'creator_node>(&mut self, caller: Weak) {
+ pub fn update_caller<'creator_node>(&mut self, caller: Rc) {
self.caller = caller;
}
@@ -136,19 +140,15 @@ impl Scope {
// This breaks any latent references, invalidating every pointer referencing into it.
self.frames.next().bump.reset();
+ log::debug!("clearing listeners!");
// Remove all the outdated listeners
self.listeners.borrow_mut().clear();
unsafe { self.hooks.reset() };
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
- 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) };
@@ -176,28 +176,42 @@ impl Scope {
// Convert the raw ptr into an actual object
// This operation is assumed to be safe
- log::debug!("Calling listeners! {:?}", self.listeners.borrow().len());
- let mut listners = self.listeners.borrow_mut();
- let (_, listener) = listners
- .iter()
- .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
- ));
+ log::debug!(
+ "There are {:?} listeners associated with this scope {:#?}",
+ self.listeners.borrow().len(),
+ self.arena_idx
+ );
- // TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster!
- unsafe {
- let mut listener_fn = &mut **listener;
- listener_fn(event);
+ let mut listners = self.listeners.borrow_mut();
+
+ // let listener = listners.get(trigger);
+ 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(())
}
+ pub fn submit_task(&self, task: &mut Pin>>) {
+ 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> {
self.frames.current_head_node()
}
@@ -210,7 +224,7 @@ impl Scope {
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
}
}
diff --git a/packages/core/src/styles.rs b/packages/core/src/styles.rs
index 433ea55f0..e16fa84da 100644
--- a/packages/core/src/styles.rs
+++ b/packages/core/src/styles.rs
@@ -12,10 +12,7 @@
//!
//!
-use crate::{
- innerlude::Attribute,
- nodebuilder::{text3, NodeFactory},
-};
+use crate::{innerlude::Attribute, nodebuilder::NodeFactory};
pub struct StyleBuilder;
pub trait AsAttr<'a> {
diff --git a/packages/core/src/tasks.rs b/packages/core/src/tasks.rs
index febdaff41..22a31e88c 100644
--- a/packages/core/src/tasks.rs
+++ b/packages/core/src/tasks.rs
@@ -16,14 +16,16 @@ use std::{
task::{Context, Poll},
};
-use futures::{Future, Stream, StreamExt};
+use futures_util::{Future, Stream, StreamExt};
use slotmap::{DefaultKey, SlotMap};
use crate::{events::EventTrigger, prelude::ScopeIdx};
+pub type TaskSubmitter = Arc;
+
pub struct TaskQueue {
slots: Arc>>,
- submitter: Arc,
+ submitter: TaskSubmitter,
}
impl TaskQueue {
@@ -34,11 +36,16 @@ impl TaskQueue {
let submitter = Arc::new(move |task| {
let mut slots = slots2.write().unwrap();
+ log::debug!("Task submitted into global task queue");
slots.insert(task);
});
Self { slots, submitter }
}
+ pub fn new_submitter(&self) -> TaskSubmitter {
+ self.submitter.clone()
+ }
+
pub fn submit_task(&mut self, task: DTask) -> TaskHandle {
let key = self.slots.write().unwrap().insert(task);
TaskHandle { key }
@@ -126,43 +133,44 @@ impl DTask {
}
}
-mod tests {
- use std::time::Duration;
+// #[cfg(test)]
+// mod tests {
+// use std::time::Duration;
- use super::*;
- use bumpalo::Bump;
+// use super::*;
+// use bumpalo::Bump;
- #[async_std::test]
- async fn example() {
- let bump = Bump::new();
- type RawTask = Pin>>;
- // build the three
- let f1 = bump.alloc(Box::pin(async {
- //
- async_std::task::sleep(Duration::from_secs(3)).await;
- println!("3 sec")
- }) as RawTask);
+// #[async_std::test]
+// async fn example() {
+// let bump = Bump::new();
+// type RawTask = Pin>>;
+// // build the three
+// let f1 = bump.alloc(Box::pin(async {
+// //
+// async_std::task::sleep(Duration::from_secs(3)).await;
+// println!("3 sec")
+// }) as RawTask);
- let f2 = bump.alloc(Box::pin(async {
- //
- async_std::task::sleep(Duration::from_secs(2)).await;
- println!("2 sec")
- }) as RawTask);
+// let f2 = bump.alloc(Box::pin(async {
+// //
+// async_std::task::sleep(Duration::from_secs(2)).await;
+// println!("2 sec")
+// }) as RawTask);
- let f3 = bump.alloc(Box::pin(async {
- //
- async_std::task::sleep(Duration::from_secs(1)).await;
- println!("1 sec");
- }) as RawTask);
+// let f3 = bump.alloc(Box::pin(async {
+// //
+// async_std::task::sleep(Duration::from_secs(1)).await;
+// println!("1 sec");
+// }) as RawTask);
- let mut queue = TaskQueue::new();
- queue.submit_task(DTask::debug_new(f1));
- queue.submit_task(DTask::debug_new(f2));
- queue.submit_task(DTask::debug_new(f3));
+// let mut queue = TaskQueue::new();
+// queue.submit_task(DTask::debug_new(f1));
+// queue.submit_task(DTask::debug_new(f2));
+// queue.submit_task(DTask::debug_new(f3));
- while !queue.is_empty() {
- let next = queue.next().await;
- println!("Event received {:#?}", next);
- }
- }
-}
+// while !queue.is_empty() {
+// let next = queue.next().await;
+// println!("Event received {:#?}", next);
+// }
+// }
+// }
diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs
index 6a6614e1e..4ae22a923 100644
--- a/packages/core/src/util.rs
+++ b/packages/core/src/util.rs
@@ -4,6 +4,8 @@ use std::{
vec::Drain,
};
+use futures_util::StreamExt;
+
use crate::innerlude::*;
#[derive(PartialEq, Debug, Clone, Default)]
@@ -67,18 +69,28 @@ impl RealDomNode {
pub struct DebugDom {
counter: u64,
+ logging: bool,
}
impl DebugDom {
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 {
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) {}
@@ -120,3 +132,61 @@ impl<'a> RealDom<'a> for DebugDom {
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::().await {
+// // Ok(_) => (),
+// // Err(_) => (),
+// // }
+// }
+// }) as Pin + '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;
+// }
+// }
diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs
index c8d63bdb1..af0775f6c 100644
--- a/packages/core/src/virtual_dom.rs
+++ b/packages/core/src/virtual_dom.rs
@@ -24,6 +24,9 @@ use crate::{arena::SharedArena, innerlude::*};
use appendlist::AppendList;
use slotmap::DefaultKey;
use slotmap::SlotMap;
+use std::any::Any;
+use std::cell::RefCell;
+use std::pin::Pin;
use std::{any::TypeId, fmt::Debug, rc::Rc};
pub type ScopeIdx = DefaultKey;
@@ -45,11 +48,9 @@ pub struct VirtualDom {
/// All components dump their updates into a queue to be processed
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
- #[doc(hidden)]
- _root_caller: Rc,
+ root_props: std::pin::Pin>,
/// 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(root: FC, root_props: P) -> Self {
let components = SharedArena::new(SlotMap::new());
- // Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
- // Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope.
- let _root_caller: Rc = Rc::new(move |scope: &Scope| {
- // let _root_caller: Rc> = 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);
+ let root_props: Pin> = Box::pin(root_props);
+ let props_ptr = root_props.as_ref().downcast_ref::().unwrap() as *const P;
// 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 = 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 tasks = TaskQueue::new();
+ let submitter = tasks.new_submitter();
+
let base_scope = components
.with(|arena| {
arena.insert_with_key(move |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();
+ log::debug!("base scope is {:#?}", base_scope);
+
Self {
- _root_caller,
base_scope,
event_queue,
components,
- tasks: TaskQueue::new(),
+ root_props,
+ tasks,
_root_prop_type: TypeId::of::
(),
}
}
@@ -191,6 +169,38 @@ impl VirtualDom {
// Private Methods for the 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
/// 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<()> {
diff --git a/packages/hooks/Cargo.toml b/packages/hooks/Cargo.toml
index 3696a9fa5..40b58a22e 100644
--- a/packages/hooks/Cargo.toml
+++ b/packages/hooks/Cargo.toml
@@ -7,4 +7,4 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
-dioxus-core = { path = "../core" }
+dioxus-core = { path="../core" }
diff --git a/packages/hooks/src/usestate.rs b/packages/hooks/src/usestate.rs
index c95ca80b6..bd7b8ad27 100644
--- a/packages/hooks/src/usestate.rs
+++ b/packages/hooks/src/usestate.rs
@@ -24,6 +24,14 @@ use std::{
/// 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.
///
+/// ## 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:
/// ```ignore
diff --git a/packages/ssr/Cargo.toml b/packages/ssr/Cargo.toml
index 2bf128676..3783e6bf9 100644
--- a/packages/ssr/Cargo.toml
+++ b/packages/ssr/Cargo.toml
@@ -11,6 +11,7 @@ dioxus-core = { path = "../core", version = "0.1.0", features = ["serialize"] }
[dev-dependencies]
+dioxus-html = { path = "../html" }
tide-websockets = "*"
thiserror = "1.0.23"
log = "0.4.13"
diff --git a/packages/ssr/README.md b/packages/ssr/README.md
new file mode 100644
index 000000000..451f9f250
--- /dev/null
+++ b/packages/ssr/README.md
@@ -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, "
hello world!
")
+```
diff --git a/packages/ssr/index.html b/packages/ssr/index.html
index 9021c236e..fe9879cff 100644
--- a/packages/ssr/index.html
+++ b/packages/ssr/index.html
@@ -1,102 +1,3 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+hello world!
diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs
index babdbe364..8ada48b7a 100644
--- a/packages/ssr/src/lib.rs
+++ b/packages/ssr/src/lib.rs
@@ -7,79 +7,128 @@
//! 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
//! `render` method.
-//!
-//! ```ignore
-//! fn main() {
-//! let renderer = TextRenderer::<()>::new(|_| html! {
"Hello world"
});
-//! let output = renderer.render();
-//! assert_eq!(output, "
Hello World
");
-//! }
-//! ```
-//!
-//! The `TextRenderer` is particularly useful when needing to cache a Virtual DOM in between requests
-//!
-use dioxus_core::prelude::{VNode, FC};
-pub mod tostring;
+use std::fmt::{Arguments, Display, Formatter};
-pub mod prelude {
- pub use dioxus_core::prelude::*;
+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
{
- _root_type: std::marker::PhantomData,
+///
+pub struct TextRenderer<'a> {
+ vdom: &'a VirtualDom,
}
-impl TextRenderer {
- /// Create a new text-renderer instance from a functional component root.
- /// Automatically progresses the creation of the VNode tree to completion.
- ///
- /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
- pub fn new(root: FC) -> Self {
- Self {
- _root_type: std::marker::PhantomData {},
+impl<'a> TextRenderer<'a> {
+ fn new(vdom: &'a VirtualDom) -> Self {
+ Self { vdom }
+ }
+
+ fn html_render(&self, 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 {
+ 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.
- /// This will progress the existing VDom's events to completion.
- pub fn from_vdom() -> Self {
- todo!()
+impl Display for TextRenderer<'_> {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ let root = self.vdom.base_scope();
+ let root_node = root.root();
+ self.html_render(root_node, f)
}
+}
- /// Pass new args to the root function
- pub fn update(&mut self, new_val: T) {
- todo!()
- }
+#[cfg(test)]
+mod tests {
+ 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
- pub fn update_mut(&mut self, modifier: impl Fn(&mut T)) {
- todo!()
+ #[test]
+ fn test_to_string_works() {
+ 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
- pub fn to_text(root: VNode) -> String {
- todo!()
- }
+ #[test]
+ fn test_write_to_file() {
+ use std::fs::File;
- /// Render the virtual DOM to a string
- pub fn render(&self) -> String {
- let mut buffer = String::new();
+ let mut file = File::create("index.html").unwrap();
- // iterate through the internal patch queue of virtual dom, and apply them to the buffer
- /*
- */
- todo!()
- }
+ let mut dom = VirtualDom::new(SIMPLE_APP);
+ dom.rebuild_in_place().expect("failed to run virtualdom");
- /// Render VDom to an existing buffer
- /// TODO @Jon, support non-string buffers to actually make this useful
- /// Currently, this only supports overwriting an existing buffer, instead of just
- pub fn render_mut(&self, buf: &mut String) {
- todo!()
+ file.write_fmt(format_args!("{}", TextRenderer::new(&dom)))
+ .unwrap();
}
}
diff --git a/packages/ssr/src/tostring.rs b/packages/ssr/src/tostring.rs
deleted file mode 100644
index fafc73cc0..000000000
--- a/packages/ssr/src/tostring.rs
+++ /dev/null
@@ -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());
-}
diff --git a/packages/web/.vscode/settings.json b/packages/web/.vscode/settings.json
index 80f51cff8..2b58ae251 100644
--- a/packages/web/.vscode/settings.json
+++ b/packages/web/.vscode/settings.json
@@ -1,3 +1,3 @@
{
"rust-analyzer.inlayHints.enable": false
-}
\ No newline at end of file
+}
diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml
index 73584d522..72171a09a 100644
--- a/packages/web/Cargo.toml
+++ b/packages/web/Cargo.toml
@@ -23,11 +23,12 @@ generational-arena = "0.2.8"
wasm-bindgen-test = "0.3.21"
once_cell = "1.7.2"
atoms = { path="../atoms" }
-
async-channel = "1.6.1"
anyhow = "1.0.41"
slotmap = "1.0.3"
+futures-util = "0.3.15"
+
[dependencies.web-sys]
version = "0.3.50"
features = [
@@ -72,6 +73,9 @@ crate-type = ["cdylib", "rlib"]
[dev-dependencies]
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"
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"
diff --git a/packages/web/examples/async_web.rs b/packages/web/examples/async_web.rs
new file mode 100644
index 000000000..1f2b66780
--- /dev/null
+++ b/packages/web/examples/async_web.rs
@@ -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 + '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"
+ }
+ }
+ }
+ }
+ }
+ })
+};
diff --git a/packages/web/examples/blah.rs b/packages/web/examples/blah.rs
new file mode 100644
index 000000000..d17426425
--- /dev/null
+++ b/packages/web/examples/blah.rs
@@ -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" }
+ }
+ }
+ }
+ }
+ })
+};
diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs
index f5637d9f3..93f0f8464 100644
--- a/packages/web/src/lib.rs
+++ b/packages/web/src/lib.rs
@@ -3,6 +3,8 @@
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
use dioxus::prelude::{Context, Properties, VNode};
+use futures_util::{pin_mut, Stream, StreamExt};
+
use fxhash::FxHashMap;
use web_sys::{window, Document, Element, Event, Node};
// use futures::{channel::mpsc, SinkExt, StreamExt};
@@ -70,12 +72,41 @@ impl WebsysRenderer {
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();
websys_dom.stack.push(root_node.clone());
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
}
diff --git a/packages/web/src/new.rs b/packages/web/src/new.rs
index eeb4b547d..231b43c0d 100644
--- a/packages/web/src/new.rs
+++ b/packages/web/src/new.rs
@@ -78,7 +78,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
self.stack.push(domnode.clone());
}
- fn append_child(&mut self) {
+ fn append_children(&mut self, many: u32) {
log::debug!("Called [`append_child`]");
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();
}
- fn replace_with(&mut self) {
+ fn replace_with(&mut self, many: u32) {
log::debug!("Called [`replace_with`]");
let new_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,
event: &'static str,
scope: dioxus_core::prelude::ScopeIdx,
- el_id: usize,
+ _element_id: usize,
real_id: RealDomNode,
) {
+ let (_on, event) = event.split_at(2);
let event = wasm_bindgen::intern(event);
log::debug!(
"Called [`new_event_listener`]: {}, {:?}, {}, {:?}",
event,
scope,
- el_id,
+ _element_id,
real_id
);
// attach the correct attributes to the element
@@ -215,10 +216,11 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
.dyn_ref::()
.expect(&format!("not an element: {:?}", el));
- let gi_id = scope.data().as_ffi();
+ let scope_id = scope.data().as_ffi();
el.set_attribute(
&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();
@@ -566,19 +568,20 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result {
.next()
.and_then(|f| f.parse::().ok())
.context("failed to parse gi id")?;
- let el_id = fields
- .next()
- .and_then(|f| f.parse::().ok())
- .context("failed to parse el id")?;
+ // let el_id = fields
+ // .next()
+ // .and_then(|f| f.parse::().ok())
+ // .context("failed to parse el id")?;
let real_id = fields
.next()
.and_then(|f| f.parse::().ok().map(RealDomNode::new))
.context("failed to parse real id")?;
// 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();
+ log::debug!("Triggered scope is {:#?}", triggered_scope);
Ok(EventTrigger::new(
virtual_event_from_websys_event(event),
triggered_scope,
diff --git a/packages/webview/src/dom.rs b/packages/webview/src/dom.rs
index 6c79dcd9e..602ce1c04 100644
--- a/packages/webview/src/dom.rs
+++ b/packages/webview/src/dom.rs
@@ -37,11 +37,11 @@ impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
self.edits.push(PushRoot { root: root.0 });
}
- fn append_child(&mut self) {
+ fn append_children(&mut self, many: u32) {
self.edits.push(AppendChild);
}
- fn replace_with(&mut self) {
+ fn replace_with(&mut self, many: u32) {
self.edits.push(ReplaceWith);
}