From afc40d9bf7a6bbadb022f92e1e4587ea6e23a320 Mon Sep 17 00:00:00 2001 From: YuKun Liu Date: Wed, 30 Nov 2022 11:39:13 -0800 Subject: [PATCH 01/17] Update ZH_CN.md --- notes/README/ZH_CN.md | 216 +++++++++++++++++++++--------------------- 1 file changed, 107 insertions(+), 109 deletions(-) diff --git a/notes/README/ZH_CN.md b/notes/README/ZH_CN.md index 4da075c7d..6ddf0221e 100644 --- a/notes/README/ZH_CN.md +++ b/notes/README/ZH_CN.md @@ -1,9 +1,6 @@ -
-

🌗🚀 Dioxus

-

- Frontend that scales. -

-
+

+ +

@@ -26,9 +23,7 @@ CI status -
-
Awesome Page @@ -39,29 +34,23 @@
-

- 官网 + 官方网站 | - 手册 + 代码示例 + | + 开发指南 | - 示例 -

-
- -
-

English | - 中文 + PT-BR

-
-Dioxus 是一个可移植、高性能的框架,用于在 Rust 中构建跨平台的用户界面。 +Dioxus 是一个可移植的、高性能的、符合人体工程学的框架,使用 Rust 语言构建跨平台的用户界面。 ```rust fn app(cx: Scope) -> Element { @@ -75,103 +64,112 @@ fn app(cx: Scope) -> Element { } ``` -Dioxus 可用于制作 网页程序、桌面应用、静态站点、移动端应用。 +Dioxus 可用于生成 网页前端、桌面应用、静态网站、移动端应用、TUI程序、等多类平台应用。 -Dioxus 为不同的平台都提供了很好的开发文档。 +如果你能够熟悉使用 React 框架,那 Dioxus 对你来说将非常简单。 -如果你会使用 React ,那 Dioxus 对你来说会很简单。 +## 独特的特性: +- 桌面程序完全基于本地环境运行(并非 Electron 的封装) +- 符合人体工程学的设计以及拥有强大的状态管理 +- 全面的内联文档 - 包含所有 HTML 元素、监听器 和 事件 指南。 +- 极快的运行效率和极高的内存效率 +- 智能项目热更新和高效的项目迭代 +- 一流的异步支持🔥 +- 更多内容请查看 [版本发布信息](https://dioxuslabs.com/blog/introducing-dioxus/). -### 项目特点: -- 对桌面应用的原生支持。 -- 强大的状态管理工具。 -- 支持所有 HTML 标签,监听器和事件。 -- 超高的内存使用率,稳定的组件分配器。 -- 多通道异步调度器,超强的异步支持。 -- 更多信息请查阅: [版本发布文档](https://dioxuslabs.com/blog/introducing-dioxus/). - -### 示例 - -本项目中的所有例子都是 `桌面应用` 程序,请使用 `cargo run --example XYZ` 运行这些例子。 - -``` -cargo run --example EXAMPLE -``` - -## 进入学习 - - - - - - - - - +## 已支持的平台 +
+
教程网页端桌面端SSR移动端状态管理
-
+ 网站项目 + + + + + + 桌面应用 + + + + + + 移动端应用 + + + + + + Liveview + + + + + + 终端程序 + + + + + + + +## Why Dioxus? + +目前有非常多的应用开发选择,为什么偏偏要选择 Dioxus 呢? + +首先,Dioxus将开发者的经验放在首位。这反映在 Dioxus 特有的各种功能上。 + +- 自动格式化 RSX 格式代码,并拥有 VSCode 插件作为支持。 +- 热加载基于 RSX 代码解析器,同时支持桌面程序和网页程序。 +- 强调文档的重要性,我们对所有 HTML 元素都提供文档支持。 + +Dioxus 也是一个可扩展化的平台。 + +- 通过实现一个非常简单的优化堆栈机,轻松构建新的渲染器。 +- 构建并分享开发者自定义的组件代码。 + +Dioxus 那么优秀,但什么时候它不适合我呢? +- 它还没有完全成熟。api仍在变化,可能会出现故障(尽管我们试图避免) +- 您需要运行在 no-std 的环境之中。 +- 你不喜欢使用 React-like 的方式构建 UI 项目。 -## Dioxus 项目 - -| 文件浏览器 (桌面应用) | WiFi 扫描器 (桌面应用) | Todo管理 (所有平台) | 商城系统 (SSR/liveview) | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| [![File Explorer](https://github.com/DioxusLabs/example-projects/raw/master/file-explorer/image.png)](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer) | [![Wifi Scanner Demo](https://github.com/DioxusLabs/example-projects/raw/master/wifi-scanner/demo_small.png)](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner) | [![TodoMVC example](https://github.com/DioxusLabs/example-projects/raw/master/todomvc/example.png)](https://github.com/DioxusLabs/example-projects/blob/master/todomvc) | [![E-commerce Example](https://github.com/DioxusLabs/example-projects/raw/master/ecommerce-site/demo.png)](https://github.com/DioxusLabs/example-projects/blob/master/ecommerce-site) | +## 贡献代码 +- 在我们的 [问题追踪](https://github.com/dioxuslabs/dioxus/issues) 中汇报你遇到的问题。 +- 加入我们的 Discord 与我们交流。 -查看 [awesome-dioxus](https://github.com/DioxusLabs/awesome-dioxus) 查看更多有趣(~~NiuBi~~)的项目! + + + -## 为什么使用 Dioxus 和 Rust ? +## License +This project is licensed under the [MIT license]. -TypeScript 是一个不错的 JavaScript 拓展集,但它仍然算是 JavaScript。 +[mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT -TS 代码运行效率不高,而且有大量的配置项。 - -相比之下,Dioxus 使用 Rust 编写将大大的提高效能。 - -使用 Rust 开发,我们能获得: - -- 静态类型支持。 -- 变量默认不变性。 -- 简单直观的模块系统。 -- 内部集成的文档系统。 -- 先进的模式匹配系统。 -- 简洁、高效、强大的迭代器。 -- 内置的 单元测试 / 集成测试。 -- 优秀的异常处理系统。 -- 强大且健全的标准库。 -- 灵活的 `宏` 系统。 -- 使用 `crates.io` 管理包。 - -Dioxus 能为开发者提供的: - -- 安全使用数据结构。 -- 安全的错误处理结果。 -- 拥有原生移动端的性能。 -- 直接访问系统的IO层。 - -Dioxus 使 Rust 应用程序的编写速度和 React 应用程序一样快,但提供了更多的健壮性,让团队能在更短的时间内做出强大功能。 - -### 不建议使用 Dioxus 的情况? - -您不该在这些情况下使用 Dioxus : - -- 您不喜欢类似 React 的开发风格。 -- 您需要一个 `no-std` 的渲染器。 -- 您希望应用运行在 `不支持 Wasm 或 asm.js` 的浏览器。 -- 您需要一个 `Send + Sync` UI 解决方案(目前不支持)。 - -### 项目生态 - -想要加入我们一起为 Dioxus 生态努力吗?有很多项目都能在您的帮助下获得改变: - -- [TUI 渲染器](https://github.com/dioxusLabs/rink) -- [CLI 开发工具](https://github.com/dioxusLabs/cli) -- [官网及文档](https://github.com/dioxusLabs/docsite) -- 动态网站 及 Web 服务器 -- 资源系统 - -## 协议 - -这个项目使用 [MIT 协议]. - -[MIT 协议]: https://github.com/dioxuslabs/dioxus/blob/master/LICENSE +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in Dioxus by you, shall be licensed as MIT, without any additional +terms or conditions. From 250d138bf67ee90a90b175f79ce52011a0ddda2f Mon Sep 17 00:00:00 2001 From: YuKun Liu Date: Thu, 1 Dec 2022 09:06:53 -0800 Subject: [PATCH 02/17] Update ZH_CN.md --- notes/README/ZH_CN.md | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/notes/README/ZH_CN.md b/notes/README/ZH_CN.md index 6ddf0221e..07bcdee55 100644 --- a/notes/README/ZH_CN.md +++ b/notes/README/ZH_CN.md @@ -165,11 +165,10 @@ Dioxus 那么优秀,但什么时候它不适合我呢? -## License -This project is licensed under the [MIT license]. +## 开源协议 + +本项目使用 [MIT license]. [mit license]: https://github.com/DioxusLabs/dioxus/blob/master/LICENSE-MIT -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in Dioxus by you, shall be licensed as MIT, without any additional -terms or conditions. +除非您另有明确声明,否则有意提交的任何贡献将被授权为 MIT 协议,没有任何附加条款或条件。 From b0cccce60a746ffb249186c52114c8b7d50ea426 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 5 Dec 2022 13:03:52 -0800 Subject: [PATCH 03/17] feat: miri passing --- packages/core/src/arena.rs | 79 ++++++++++++++++------------ packages/core/src/nodes.rs | 8 +++ packages/core/src/scope_arena.rs | 37 ------------- packages/core/src/scopes.rs | 22 ++++---- packages/core/src/virtual_dom.rs | 2 + packages/core/tests/bubble_error.rs | 4 +- packages/core/tests/context_api.rs | 4 +- packages/core/tests/miri_full_app.rs | 10 ++-- packages/core/tests/miri_stress.rs | 2 +- packages/core/tests/suspense.rs | 7 +-- 10 files changed, 79 insertions(+), 96 deletions(-) diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index d6af9b051..eb0d3cc95 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,7 +1,4 @@ -use crate::{ - nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode, - ScopeId, -}; +use crate::{nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, DynamicNode, ScopeId}; use bumpalo::boxed::Box as BumpBox; /// An Element's unique identifier. @@ -64,12 +61,11 @@ impl VirtualDom { pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option { if el.0 == 0 { panic!( - "Invalid element set to 0 - {:#?}", + "Cannot reclaim the root element - {:#?}", std::backtrace::Backtrace::force_capture() - ) + ); } - println!("reclaiming {:?}", el); self.elements.try_remove(el.0) } @@ -80,46 +76,61 @@ impl VirtualDom { // Drop a scope and all its children pub(crate) fn drop_scope(&mut self, id: ScopeId) { - let scope = self.scopes.get(id.0).unwrap(); - - if let Some(root) = scope.as_ref().try_root_node() { - let root = unsafe { root.extend_lifetime_ref() }; - if let RenderReturn::Sync(Ok(node)) = root { + if let Some(root) = self.scopes[id.0].as_ref().try_root_node() { + if let RenderReturn::Sync(Ok(node)) = unsafe { root.extend_lifetime_ref() } { self.drop_scope_inner(node) } } - let scope = self.scopes.get_mut(id.0).unwrap(); - scope.props.take(); + self.scopes[id.0].props.take(); // Drop all the hooks once the children are dropped // this means we'll drop hooks bottom-up - for hook in scope.hook_list.get_mut().drain(..) { - println!("dropping hook !"); - drop(unsafe { BumpBox::from_raw(hook) }); - println!("hook dropped !"); - } + self.scopes[id.0] + .hook_list + .get_mut() + .drain(..) + .for_each(|hook| drop(unsafe { BumpBox::from_raw(hook) })); } fn drop_scope_inner(&mut self, node: &VNode) { - for attr in node.dynamic_attrs { - if let AttributeValue::Listener(l) = &attr.value { - l.borrow_mut().take(); - } - } + node.clear_listeners(); + node.dynamic_nodes.iter().for_each(|node| match node { + DynamicNode::Component(c) => self.drop_scope(c.scope.get().unwrap()), + DynamicNode::Fragment(nodes) => nodes + .into_iter() + .for_each(|node| self.drop_scope_inner(node)), + _ => {} + }) + } - for (idx, _) in node.template.roots.iter().enumerate() { - match node.dynamic_root(idx) { - Some(DynamicNode::Component(c)) => self.drop_scope(c.scope.get().unwrap()), - Some(DynamicNode::Fragment(nodes)) => { - for node in *nodes { - self.drop_scope_inner(node); - } - } - _ => {} - } + /// Descend through the tree, removing any borrowed props and listeners + pub(crate) fn ensure_drop_safety(&self, scope: ScopeId) { + let node = unsafe { self.scopes[scope.0].previous_frame().try_load_node() }; + + // And now we want to make sure the previous frame has dropped anything that borrows self + if let Some(RenderReturn::Sync(Ok(node))) = node { + self.ensure_drop_safety_inner(node); } } + + fn ensure_drop_safety_inner(&self, node: &VNode) { + node.clear_listeners(); + + node.dynamic_nodes.iter().for_each(|child| match child { + // Only descend if the props are borrowed + DynamicNode::Component(c) if !c.static_props => { + self.ensure_drop_safety(c.scope.get().unwrap()); + c.props.set(None); + } + + DynamicNode::Fragment(f) => f + .iter() + .for_each(|node| self.ensure_drop_safety_inner(node)), + + _ => {} + }); + } } impl ElementPath { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 27eed58d4..37e2d0d77 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -84,6 +84,14 @@ impl<'a> VNode<'a> { } } } + + pub(crate) fn clear_listeners(&self) { + for attr in self.dynamic_attrs { + if let AttributeValue::Listener(l) = &attr.value { + l.borrow_mut().take(); + } + } + } } /// A static layout of a UI tree that describes a set of dynamic and static nodes. diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index e94818fee..68aa13450 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -7,7 +7,6 @@ use crate::{ scheduler::RcWake, scopes::{ScopeId, ScopeState}, virtual_dom::VirtualDom, - AttributeValue, DynamicNode, VNode, }; use futures_util::FutureExt; use std::{ @@ -49,42 +48,6 @@ impl VirtualDom { .and_then(|id| self.scopes.get_mut(id.0).map(|f| f.as_mut() as *mut _)) } - fn ensure_drop_safety(&self, scope: ScopeId) { - let scope = &self.scopes[scope.0]; - let node = unsafe { scope.previous_frame().try_load_node() }; - - // And now we want to make sure the previous frame has dropped anything that borrows self - if let Some(RenderReturn::Sync(Ok(node))) = node { - self.ensure_drop_safety_inner(node); - } - } - - fn ensure_drop_safety_inner(&self, node: &VNode) { - for attr in node.dynamic_attrs { - if let AttributeValue::Listener(l) = &attr.value { - l.borrow_mut().take(); - } - } - - for child in node.dynamic_nodes { - match child { - DynamicNode::Component(c) => { - // Only descend if the props are borrowed - if !c.static_props { - self.ensure_drop_safety(c.scope.get().unwrap()); - c.props.set(None); - } - } - DynamicNode::Fragment(f) => { - for node in *f { - self.ensure_drop_safety_inner(node); - } - } - _ => {} - } - } - } - pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn { // Cycle to the next frame and then reset it // This breaks any latent references, invalidating every pointer referencing into it. diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index bb2776861..6e048638b 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -258,8 +258,6 @@ impl<'src> ScopeState { } /// Try to retrieve a shared state with type `T` from any parent scope. - /// - /// To release the borrow, use `cloned` if the context is clone. pub fn consume_context(&self) -> Option<&T> { if let Some(this_ctx) = self.has_context() { return Some(this_ctx); @@ -307,17 +305,17 @@ impl<'src> ScopeState { pub fn provide_context(&self, value: T) -> &T { let mut contexts = self.shared_contexts.borrow_mut(); - let any = match contexts.get(&TypeId::of::()) { - Some(item) => item.downcast_ref::().unwrap() as *const T, - None => { - let boxed = Box::new(value); - let boxed_ptr = boxed.as_ref() as *const T; - contexts.insert(TypeId::of::(), boxed); - boxed_ptr - } - }; + if !contexts.contains_key(&TypeId::of::()) { + contexts.insert(TypeId::of::(), Box::new(value)); + } - unsafe { &*any } + let out = contexts + .get(&TypeId::of::()) + .unwrap() + .downcast_ref::() + .unwrap() as *const T; + + unsafe { &*out } } /// Pushes the future onto the poll queue to be polled after the component renders. diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a7046636b..1c1d6aa15 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -629,6 +629,8 @@ impl VirtualDom { impl Drop for VirtualDom { fn drop(&mut self) { + println!("Dropping virtualdom"); + // Simply drop this scope which drops all of its children self.drop_scope(ScopeId(0)); } diff --git a/packages/core/tests/bubble_error.rs b/packages/core/tests/bubble_error.rs index 6dbd0f3f7..aa23ab718 100644 --- a/packages/core/tests/bubble_error.rs +++ b/packages/core/tests/bubble_error.rs @@ -20,9 +20,9 @@ fn app(cx: Scope) -> Element { fn it_goes() { let mut dom = VirtualDom::new(app); - let edits = dom.rebuild().santize(); + let _edits = dom.rebuild().santize(); dom.mark_dirty(ScopeId(0)); - dom.render_immediate(); + _ = dom.render_immediate(); } diff --git a/packages/core/tests/context_api.rs b/packages/core/tests/context_api.rs index 696810cb0..4040daccb 100644 --- a/packages/core/tests/context_api.rs +++ b/packages/core/tests/context_api.rs @@ -29,11 +29,11 @@ fn state_shares() { dom.mark_dirty(ScopeId(0)); _ = dom.render_immediate(); - assert_eq!(dom.base_scope().consume_context::().unwrap(), 1); + assert_eq!(dom.base_scope().consume_context::().unwrap(), &1); dom.mark_dirty(ScopeId(0)); _ = dom.render_immediate(); - assert_eq!(dom.base_scope().consume_context::().unwrap(), 2); + assert_eq!(dom.base_scope().consume_context::().unwrap(), &2); dom.mark_dirty(ScopeId(2)); assert_eq!( diff --git a/packages/core/tests/miri_full_app.rs b/packages/core/tests/miri_full_app.rs index 6cd3d2bd8..c1c6d52cc 100644 --- a/packages/core/tests/miri_full_app.rs +++ b/packages/core/tests/miri_full_app.rs @@ -8,16 +8,16 @@ fn miri_rollover() { _ = dom.rebuild(); - for x in 0..3 { + for _ in 0..3 { dom.handle_event("click", Rc::new(MouseData::default()), ElementId(2), true); dom.process_events(); - dom.render_immediate(); + _ = dom.render_immediate(); } } fn app(cx: Scope) -> Element { let mut idx = use_state(cx, || 0); - let onhover = |h| println!("go!"); + let onhover = |_| println!("go!"); cx.render(rsx! { div { @@ -31,7 +31,7 @@ fn app(cx: Scope) -> Element { button { onclick: move |_| idx -= 1, "-" } ul { (0..**idx).map(|i| rsx! { - Child { i: i, onhover: onhover } + child_example { i: i, onhover: onhover } }) } } @@ -39,7 +39,7 @@ fn app(cx: Scope) -> Element { } #[inline_props] -fn Child<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element { +fn child_example<'a>(cx: Scope<'a>, i: i32, onhover: EventHandler<'a, MouseEvent>) -> Element { cx.render(rsx! { li { onmouseover: move |e| onhover.call(e), diff --git a/packages/core/tests/miri_stress.rs b/packages/core/tests/miri_stress.rs index 3ebd5c504..3105e45bc 100644 --- a/packages/core/tests/miri_stress.rs +++ b/packages/core/tests/miri_stress.rs @@ -97,7 +97,7 @@ fn memo_works_properly() { let mut dom = VirtualDom::new(app); - dom.rebuild(); + _ = dom.rebuild(); todo!() // dom.hard_diff(ScopeId(0)); // dom.hard_diff(ScopeId(0)); diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index 2da343707..d3fb776a9 100644 --- a/packages/core/tests/suspense.rs +++ b/packages/core/tests/suspense.rs @@ -1,9 +1,8 @@ use dioxus::core::ElementId; use dioxus::core::{Mutation::*, SuspenseContext}; use dioxus::prelude::*; -use dioxus_core::SuspenseContext; use std::future::IntoFuture; -use std::{rc::Rc, time::Duration}; +use std::time::Duration; #[tokio::test] async fn it_works() { @@ -51,7 +50,9 @@ fn app(cx: Scope) -> Element { } fn suspense_boundary(cx: Scope) -> Element { - cx.use_hook(|| cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id())))); + cx.use_hook(|| { + cx.provide_context(SuspenseContext::new(cx.scope_id())); + }); // Ensure the right types are found cx.has_context::().unwrap(); From e22923eb2d316b01e0e30bfa1a40c95fb5e95b87 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 5 Dec 2022 14:16:54 -0800 Subject: [PATCH 04/17] feat: core tests passing --- packages/core/README.md | 6 +- packages/core/src/arena.rs | 35 +++++++---- packages/core/src/create.rs | 7 +-- packages/core/src/diff.rs | 74 ++++++++++-------------- packages/core/src/nodes.rs | 2 + packages/core/src/scheduler/wait.rs | 15 +++-- packages/core/src/scopes.rs | 38 ++++++------ packages/core/src/virtual_dom.rs | 9 --- packages/core/tests/bubble_error.rs | 2 +- packages/core/tests/context_api.rs | 10 +++- packages/core/tests/diff_keyed_list.rs | 8 +-- packages/core/tests/diff_unkeyed_list.rs | 12 ++-- packages/core/tests/miri_stress.rs | 2 +- packages/core/tests/suspense.rs | 1 - packages/hooks/src/usecontext.rs | 8 +-- 15 files changed, 107 insertions(+), 122 deletions(-) diff --git a/packages/core/README.md b/packages/core/README.md index cf3467030..d3ad1946f 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -41,7 +41,7 @@ fn app(cx: Scope) -> Element { Then, we'll want to create a new VirtualDom using this app as the root component. -```rust, ingore +```rust, ignore let mut dom = VirtualDom::new(app); ``` @@ -65,9 +65,9 @@ dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16))); If an event occurs from outside the virtualdom while waiting for work, then we can cancel the wait using a `select!` block and inject the event. -```rust +```rust, ignore loop { - tokio::select! { + select! { evt = real_dom.event() => dom.handle_event("click", evt.data, evt.element, evt.bubbles), _ = dom.wait_for_work() => {} } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index eb0d3cc95..bce159dd6 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -34,21 +34,22 @@ impl ElementRef { impl VirtualDom { pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId { - let entry = self.elements.vacant_entry(); - let id = entry.key(); - entry.insert(ElementRef { - template: template as *const _ as *mut _, - path: ElementPath::Deep(path), - }); - ElementId(id) + self.next(template, ElementPath::Deep(path)) } pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId { + self.next(template, ElementPath::Root(path)) + } + + fn next(&mut self, template: &VNode, path: ElementPath) -> ElementId { let entry = self.elements.vacant_entry(); let id = entry.key(); + + println!("claiming {:?}", id); + entry.insert(ElementRef { template: template as *const _ as *mut _, - path: ElementPath::Root(path), + path, }); ElementId(id) } @@ -66,6 +67,8 @@ impl VirtualDom { ); } + println!("Reclaiming {:?}", el.0); + self.elements.try_remove(el.0) } @@ -100,8 +103,20 @@ impl VirtualDom { DynamicNode::Fragment(nodes) => nodes .into_iter() .for_each(|node| self.drop_scope_inner(node)), - _ => {} - }) + DynamicNode::Placeholder(t) => { + self.try_reclaim(t.get()); + } + DynamicNode::Text(t) => { + self.try_reclaim(t.id.get()); + } + }); + + for root in node.root_ids { + let id = root.get(); + if id.0 != 0 { + self.try_reclaim(id); + } + } } /// Descend through the tree, removing any borrowed props and listeners diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index feaa276e4..e8886caf6 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -341,7 +341,7 @@ impl<'b> VirtualDom { // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense let boundary = match self.scopes[scope.0].has_context::() { - Some(boundary) => unsafe { &*(boundary as *const SuspenseContext) }, + Some(boundary) => boundary, _ => return created, }; @@ -388,11 +388,6 @@ impl<'b> VirtualDom { // Set the placeholder of the scope self.scopes[scope.0].placeholder.set(Some(new_id)); - println!( - "assigning id {:?} to path {:?}, template: {:?}", - new_id, &template.template.node_paths, template.template - ); - // Since the placeholder is already in the DOM, we don't create any new nodes self.mutations.push(AssignId { id: new_id, diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index c0b14d0bd..fca6a45e0 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -25,53 +25,45 @@ impl<'b> VirtualDom { .previous_frame() .try_load_node() .expect("Call rebuild before diffing"); + let new = scope_state .current_frame() .try_load_node() .expect("Call rebuild before diffing"); - self.diff_maybe_node(old, new); + + use RenderReturn::{Async, Sync}; + + match (old, new) { + (Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r), + + // Err cases + (Sync(Ok(l)), Sync(Err(e))) => self.diff_ok_to_err(l, e), + (Sync(Err(e)), Sync(Ok(r))) => self.diff_err_to_ok(e, r), + (Sync(Err(_eo)), Sync(Err(_en))) => { /* nothing */ } + + // Async + (Sync(Ok(_l)), Async(_)) => todo!(), + (Sync(Err(_e)), Async(_)) => todo!(), + (Async(_), Sync(Ok(_r))) => todo!(), + (Async(_), Sync(Err(_e))) => { /* nothing */ } + (Async(_), Async(_)) => { /* nothing */ } + }; } self.scope_stack.pop(); } - fn diff_maybe_node(&mut self, old: &'b RenderReturn<'b>, new: &'b RenderReturn<'b>) { - use RenderReturn::{Async, Sync}; - match (old, new) { - (Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r), - - // Err cases - (Sync(Ok(l)), Sync(Err(e))) => self.diff_ok_to_err(l, e), - (Sync(Err(e)), Sync(Ok(r))) => self.diff_err_to_ok(e, r), - (Sync(Err(_eo)), Sync(Err(_en))) => { /* nothing */ } - - // Async - (Sync(Ok(_l)), Async(_)) => todo!(), - (Sync(Err(_e)), Async(_)) => todo!(), - (Async(_), Sync(Ok(_r))) => todo!(), - (Async(_), Sync(Err(_e))) => { /* nothing */ } - (Async(_), Async(_)) => { /* nothing */ } - } - } - fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _e: &anyhow::Error) { - todo!("Not yet handling error rollover") + return; } fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) { - todo!("Not yet handling error rollover") + return; } fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) { - println!( - "diffing node {:#?},\n\n{:#?}", - left_template.template.name, right_template.template.name - ); - if left_template.template.name != right_template.template.name { return self.light_diff_templates(left_template, right_template); } - println!("diffing attributs..."); - for (left_attr, right_attr) in left_template .dynamic_attrs .iter() @@ -106,8 +98,6 @@ impl<'b> VirtualDom { } } - println!("diffing dyn nodes..."); - for (idx, (left_node, right_node)) in left_template .dynamic_nodes .iter() @@ -133,8 +123,6 @@ impl<'b> VirtualDom { }; } - println!("applying roots..."); - // Make sure the roots get transferred over for (left, right) in left_template .root_ids @@ -153,16 +141,18 @@ impl<'b> VirtualDom { } fn replace_nodes_with_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b Cell) { + // Remove the old nodes, except for one + self.remove_nodes(&l[1..]); + + // Now create the new one + let first = self.replace_inner(&l[0]); + // Create the placeholder first, ensuring we get a dedicated ID for the placeholder let placeholder = self.next_element(&l[0], &[]); r.set(placeholder); self.mutations .push(Mutation::CreatePlaceholder { id: placeholder }); - // Remove the old nodes, except for onea - let first = self.replace_inner(&l[0]); - self.remove_nodes(&l[1..]); - self.mutations .push(Mutation::ReplaceWith { id: first, m: 1 }); @@ -178,7 +168,6 @@ impl<'b> VirtualDom { ) { // Replace components that have different render fns if left.render_fn != right.render_fn { - dbg!(&left, &right); let created = self.create_component_node(right_template, right, idx); let head = unsafe { self.scopes[left.scope.get().unwrap().0] @@ -396,8 +385,6 @@ impl<'b> VirtualDom { "all siblings must be keyed or all siblings must be non-keyed" ); - println!("Diffing fragment {:?}, {:?}", old.len(), new.len()); - if new_is_keyed && old_is_keyed { self.diff_keyed_children(old, new); } else { @@ -420,9 +407,7 @@ impl<'b> VirtualDom { debug_assert!(!new.is_empty()); debug_assert!(!old.is_empty()); - println!("Diffing non keyed children"); - - match dbg!(old.len().cmp(&new.len())) { + match old.len().cmp(&new.len()) { Ordering::Greater => self.remove_nodes(&old[new.len()..]), Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()), Ordering::Equal => {} @@ -760,7 +745,8 @@ impl<'b> VirtualDom { /// Remove these nodes from the dom /// Wont generate mutations for the inner nodes fn remove_nodes(&mut self, nodes: &'b [VNode<'b>]) { - nodes.iter().for_each(|node| self.remove_node(node)); + // note that we iterate in reverse to unlink lists of nodes in their rough index order + nodes.iter().rev().for_each(|node| self.remove_node(node)); } fn remove_node(&mut self, node: &'b VNode<'b>) { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 37e2d0d77..a7968abf5 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -424,6 +424,8 @@ impl<'a> IntoDynNode<'a> for VNode<'a> { DynamicNode::Fragment(_cx.bump().alloc([self])) } } + +// An element that's an error is currently lost into the ether impl<'a> IntoDynNode<'a> for Element<'a> { fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> { match self { diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index e13b0fd45..cda8a9549 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -1,5 +1,8 @@ use futures_util::FutureExt; -use std::task::{Context, Poll}; +use std::{ + rc::Rc, + task::{Context, Poll}, +}; use crate::{ innerlude::{Mutation, Mutations, SuspenseContext}, @@ -31,17 +34,13 @@ impl VirtualDom { } } - pub(crate) fn acquire_suspense_boundary<'a>(&self, id: ScopeId) -> &'a SuspenseContext { - let ct = self.scopes[id.0] + pub(crate) fn acquire_suspense_boundary<'a>(&self, id: ScopeId) -> Rc { + self.scopes[id.0] .consume_context::() - .unwrap(); - - unsafe { &*(ct as *const SuspenseContext) } + .unwrap() } pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) { - println!("suspense notified"); - let leaf = self .scheduler .leaves diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 6e048638b..b2863b9e5 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -81,7 +81,7 @@ pub struct ScopeState { pub(crate) hook_list: RefCell>, pub(crate) hook_idx: Cell, - pub(crate) shared_contexts: RefCell>>, + pub(crate) shared_contexts: RefCell>>, pub(crate) tasks: Rc, pub(crate) spawned_tasks: HashSet, @@ -250,15 +250,17 @@ impl<'src> ScopeState { } /// Return any context of type T if it exists on this scope - pub fn has_context(&self) -> Option<&T> { - let contextex = self.shared_contexts.borrow(); - let val = contextex.get(&TypeId::of::())?; - let as_concrete = val.downcast_ref::()? as *const T; - Some(unsafe { &*as_concrete }) + pub fn has_context(&self) -> Option> { + self.shared_contexts + .borrow() + .get(&TypeId::of::())? + .clone() + .downcast() + .ok() } /// Try to retrieve a shared state with type `T` from any parent scope. - pub fn consume_context(&self) -> Option<&T> { + pub fn consume_context(&self) -> Option> { if let Some(this_ctx) = self.has_context() { return Some(this_ctx); } @@ -268,8 +270,7 @@ impl<'src> ScopeState { // safety: all parent pointers are valid thanks to the bump arena let parent = unsafe { &*parent_ptr }; if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::()) { - let as_concrete = shared.downcast_ref::()? as *const T; - return Some(unsafe { &*as_concrete }); + return shared.clone().downcast().ok(); } search_parent = parent.parent; } @@ -281,7 +282,7 @@ impl<'src> ScopeState { /// /// This is a "fundamental" operation and should only be called during initialization of a hook. /// - /// For a hook that provides the same functionality, use `use_provide_context` and `use_consume_context` instead. + /// For a hook that provides the same functionality, use `use_provide_context` and `use_context` instead. /// /// If a state is provided that already exists, the new value will not be inserted. Instead, this method will /// return the existing value. This behavior is chosen so shared values do not need to be `Clone`. This particular @@ -302,20 +303,15 @@ impl<'src> ScopeState { /// render!(div { "hello {state.0}" }) /// } /// ``` - pub fn provide_context(&self, value: T) -> &T { + pub fn provide_context(&self, value: T) -> Rc { let mut contexts = self.shared_contexts.borrow_mut(); - - if !contexts.contains_key(&TypeId::of::()) { - contexts.insert(TypeId::of::(), Box::new(value)); - } - - let out = contexts + contexts.insert(TypeId::of::(), Rc::new(value)); + contexts .get(&TypeId::of::()) .unwrap() - .downcast_ref::() - .unwrap() as *const T; - - unsafe { &*out } + .clone() + .downcast() + .unwrap() } /// Pushes the future onto the poll queue to be polled after the component renders. diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 1c1d6aa15..dbcba7548 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -362,8 +362,6 @@ impl VirtualDom { let target_path = el_ref.path; for (idx, attr) in template.dynamic_attrs.iter().enumerate() { - println!("{:?} \n {:?} \n {:?}", attr, name, element); - let this_path = template.template.attr_paths[idx]; // listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing @@ -385,8 +383,6 @@ impl VirtualDom { } } - println!("calling listeners: {:?}", listeners); - // Now that we've accumulated all the parent attributes for the target element, call them in reverse order // We check the bubble state between each call to see if the event has been stopped from bubbling for listener in listeners.drain(..).rev() { @@ -403,8 +399,6 @@ impl VirtualDom { parent_path = template.parent.and_then(|id| self.elements.get(id.0)); } - - println!("all listeners exhausted"); } /// Wait for the scheduler to have any work. @@ -527,7 +521,6 @@ impl VirtualDom { /// /// If no suspense trees are present pub async fn render_with_deadline(&mut self, deadline: impl Future) -> Mutations { - println!("render with deadline"); pin_mut!(deadline); loop { @@ -629,8 +622,6 @@ impl VirtualDom { impl Drop for VirtualDom { fn drop(&mut self) { - println!("Dropping virtualdom"); - // Simply drop this scope which drops all of its children self.drop_scope(ScopeId(0)); } diff --git a/packages/core/tests/bubble_error.rs b/packages/core/tests/bubble_error.rs index aa23ab718..3dbfe80a9 100644 --- a/packages/core/tests/bubble_error.rs +++ b/packages/core/tests/bubble_error.rs @@ -17,7 +17,7 @@ fn app(cx: Scope) -> Element { } #[test] -fn it_goes() { +fn bubbles_error() { let mut dom = VirtualDom::new(app); let _edits = dom.rebuild().santize(); diff --git a/packages/core/tests/context_api.rs b/packages/core/tests/context_api.rs index 4040daccb..39cdf4843 100644 --- a/packages/core/tests/context_api.rs +++ b/packages/core/tests/context_api.rs @@ -29,11 +29,17 @@ fn state_shares() { dom.mark_dirty(ScopeId(0)); _ = dom.render_immediate(); - assert_eq!(dom.base_scope().consume_context::().unwrap(), &1); + assert_eq!( + dom.base_scope().consume_context::().unwrap().as_ref(), + &1 + ); dom.mark_dirty(ScopeId(0)); _ = dom.render_immediate(); - assert_eq!(dom.base_scope().consume_context::().unwrap(), &2); + assert_eq!( + dom.base_scope().consume_context::().unwrap().as_ref(), + &2 + ); dom.mark_dirty(ScopeId(2)); assert_eq!( diff --git a/packages/core/tests/diff_keyed_list.rs b/packages/core/tests/diff_keyed_list.rs index ba9fe2d62..81013849d 100644 --- a/packages/core/tests/diff_keyed_list.rs +++ b/packages/core/tests/diff_keyed_list.rs @@ -320,9 +320,9 @@ fn remove_list() { assert_eq!( dom.render_immediate().santize().edits, [ - Remove { id: ElementId(3) }, + Remove { id: ElementId(5) }, Remove { id: ElementId(4) }, - Remove { id: ElementId(5) } + Remove { id: ElementId(3) }, ] ); } @@ -345,10 +345,10 @@ fn no_common_keys() { assert_eq!( dom.render_immediate().santize().edits, [ - Remove { id: ElementId(2) }, Remove { id: ElementId(3) }, - LoadTemplate { name: "template", index: 0, id: ElementId(3) }, + Remove { id: ElementId(2) }, LoadTemplate { name: "template", index: 0, id: ElementId(2) }, + LoadTemplate { name: "template", index: 0, id: ElementId(3) }, LoadTemplate { name: "template", index: 0, id: ElementId(4) }, ReplaceWith { id: ElementId(1), m: 3 } ] diff --git a/packages/core/tests/diff_unkeyed_list.rs b/packages/core/tests/diff_unkeyed_list.rs index 0ddb71a42..415aa79c0 100644 --- a/packages/core/tests/diff_unkeyed_list.rs +++ b/packages/core/tests/diff_unkeyed_list.rs @@ -357,11 +357,11 @@ fn remove_many() { assert_eq!( edits.edits, [ - Remove { id: ElementId(1,) }, - Remove { id: ElementId(5,) }, - Remove { id: ElementId(7,) }, Remove { id: ElementId(9,) }, - CreatePlaceholder { id: ElementId(9,) }, + Remove { id: ElementId(7,) }, + Remove { id: ElementId(5,) }, + Remove { id: ElementId(1,) }, + CreatePlaceholder { id: ElementId(3,) }, ReplaceWith { id: ElementId(2,), m: 1 }, ] ); @@ -372,8 +372,8 @@ fn remove_many() { edits.edits, [ LoadTemplate { name: "template", index: 0, id: ElementId(2,) }, - HydrateText { path: &[0,], value: "hello 0", id: ElementId(10,) }, - ReplaceWith { id: ElementId(9,), m: 1 }, + HydrateText { path: &[0,], value: "hello 0", id: ElementId(1,) }, + ReplaceWith { id: ElementId(3,), m: 1 }, ] ) } diff --git a/packages/core/tests/miri_stress.rs b/packages/core/tests/miri_stress.rs index 3105e45bc..2d9760e5d 100644 --- a/packages/core/tests/miri_stress.rs +++ b/packages/core/tests/miri_stress.rs @@ -98,7 +98,7 @@ fn memo_works_properly() { let mut dom = VirtualDom::new(app); _ = dom.rebuild(); - todo!() + // todo!() // dom.hard_diff(ScopeId(0)); // dom.hard_diff(ScopeId(0)); // dom.hard_diff(ScopeId(0)); diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index d3fb776a9..47c617085 100644 --- a/packages/core/tests/suspense.rs +++ b/packages/core/tests/suspense.rs @@ -73,7 +73,6 @@ async fn async_text(cx: Scope<'_>) -> Element { let age = use_future!(cx, || async { tokio::time::sleep(std::time::Duration::from_secs(2)).await; - println!("long future completed"); 1234 }); diff --git a/packages/hooks/src/usecontext.rs b/packages/hooks/src/usecontext.rs index 02902fa76..5d0f42363 100644 --- a/packages/hooks/src/usecontext.rs +++ b/packages/hooks/src/usecontext.rs @@ -2,16 +2,12 @@ use dioxus_core::ScopeState; /// Consume some context in the tree pub fn use_context(cx: &ScopeState) -> Option<&T> { - match *cx.use_hook(|| cx.consume_context::().map(|t| t as *const T)) { - Some(res) => Some(unsafe { &*res }), - None => None, - } + cx.use_hook(|| cx.consume_context::()).as_deref() } /// Provide some context via the tree and return a reference to it /// /// Once the context has been provided, it is immutable. Mutations should be done via interior mutability. pub fn use_context_provider(cx: &ScopeState, f: impl FnOnce() -> T) -> &T { - let ptr = *cx.use_hook(|| cx.provide_context(f()) as *const T); - unsafe { &*ptr } + cx.use_hook(|| cx.provide_context(f())) } From 99a818996e387d15ac909a86104bef0db5afdc74 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 5 Dec 2022 14:18:28 -0800 Subject: [PATCH 05/17] chore: make warnings go away --- packages/hooks/src/usecallback.rs | 8 +++----- packages/hooks/src/usefuture.rs | 6 +++--- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/packages/hooks/src/usecallback.rs b/packages/hooks/src/usecallback.rs index aaa7432cf..180ed2242 100644 --- a/packages/hooks/src/usecallback.rs +++ b/packages/hooks/src/usecallback.rs @@ -1,6 +1,4 @@ -use std::rc::Rc; - -use dioxus_core::{prelude::EventHandler, ScopeState}; +use dioxus_core::ScopeState; use std::future::Future; #[macro_export] @@ -30,8 +28,8 @@ where move |evt| cx.spawn(hook(evt)) } -fn it_works(cx: &ScopeState) { - let p = use_callback(cx, || { +fn _it_works(cx: &ScopeState) { + let _p = use_callback(cx, || { |()| async { // } diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index a95fc3221..9063c7db6 100644 --- a/packages/hooks/src/usefuture.rs +++ b/packages/hooks/src/usefuture.rs @@ -51,7 +51,7 @@ where // Clone in our cells let values = state.values.clone(); - let schedule_update = state.update.clone(); + let _schedule_update = state.update.clone(); let waker = state.waker.clone(); // Cancel the current future @@ -67,7 +67,7 @@ where match waker.borrow().as_ref() { Some(waker) => waker.wake_by_ref(), None => { - println!("scheduling update"); + // schedule_update() } } @@ -123,7 +123,7 @@ impl UseFuture { } // Manually set the value in the future slot without starting the future over - pub fn set(&self, new_value: T) { + pub fn set(&self, _new_value: T) { // self.slot.set(Some(new_value)); // self.needs_regen.set(true); // (self.update)(); From df4a1b6c7ab6c59a485d0821b1ad23b698cbb69d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 5 Dec 2022 15:30:49 -0800 Subject: [PATCH 06/17] chore: finish tweaking context api --- packages/core/src/create.rs | 3 ++- packages/core/src/scheduler/wait.rs | 4 +-- packages/core/src/scopes.rs | 32 +++++++++++------------ packages/core/src/virtual_dom.rs | 10 +++---- packages/fermi/src/hooks/atom_root.rs | 3 ++- packages/hooks/src/usecontext.rs | 15 +++++++---- packages/router/src/components/link.rs | 6 ++--- packages/router/src/components/route.rs | 16 +++++------- packages/router/src/components/router.rs | 11 ++++---- packages/router/src/hooks/use_route.rs | 12 ++++----- packages/router/src/hooks/use_router.rs | 12 ++++----- packages/router/src/service.rs | 33 +++++++++++------------- 12 files changed, 76 insertions(+), 81 deletions(-) diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index e8886caf6..71c5d2198 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -1,4 +1,5 @@ use std::cell::Cell; +use std::rc::Rc; use crate::innerlude::{VComponent, VText}; use crate::mutations::Mutation; @@ -340,7 +341,7 @@ impl<'b> VirtualDom { } // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense - let boundary = match self.scopes[scope.0].has_context::() { + let boundary = match self.scopes[scope.0].has_context::>() { Some(boundary) => boundary, _ => return created, }; diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index cda8a9549..6ef3704d3 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/packages/core/src/scheduler/wait.rs @@ -34,9 +34,9 @@ impl VirtualDom { } } - pub(crate) fn acquire_suspense_boundary<'a>(&self, id: ScopeId) -> Rc { + pub(crate) fn acquire_suspense_boundary<'a>(&'a self, id: ScopeId) -> Rc { self.scopes[id.0] - .consume_context::() + .consume_context::>() .unwrap() } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index b2863b9e5..79ce6f64a 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -81,7 +81,7 @@ pub struct ScopeState { pub(crate) hook_list: RefCell>, pub(crate) hook_idx: Cell, - pub(crate) shared_contexts: RefCell>>, + pub(crate) shared_contexts: RefCell>>, pub(crate) tasks: Rc, pub(crate) spawned_tasks: HashSet, @@ -250,17 +250,18 @@ impl<'src> ScopeState { } /// Return any context of type T if it exists on this scope - pub fn has_context(&self) -> Option> { + pub fn has_context(&self) -> Option { self.shared_contexts .borrow() .get(&TypeId::of::())? - .clone() - .downcast() - .ok() + .downcast_ref::() + .cloned() } /// Try to retrieve a shared state with type `T` from any parent scope. - pub fn consume_context(&self) -> Option> { + /// + /// Clones the state if it exists. + pub fn consume_context(&self) -> Option { if let Some(this_ctx) = self.has_context() { return Some(this_ctx); } @@ -270,7 +271,7 @@ impl<'src> ScopeState { // safety: all parent pointers are valid thanks to the bump arena let parent = unsafe { &*parent_ptr }; if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::()) { - return shared.clone().downcast().ok(); + return shared.downcast_ref::().cloned(); } search_parent = parent.parent; } @@ -303,15 +304,14 @@ impl<'src> ScopeState { /// render!(div { "hello {state.0}" }) /// } /// ``` - pub fn provide_context(&self, value: T) -> Rc { - let mut contexts = self.shared_contexts.borrow_mut(); - contexts.insert(TypeId::of::(), Rc::new(value)); - contexts - .get(&TypeId::of::()) - .unwrap() - .clone() - .downcast() - .unwrap() + pub fn provide_context(&self, value: T) -> T { + let value2 = value.clone(); + + self.shared_contexts + .borrow_mut() + .insert(TypeId::of::(), Box::new(value)); + + value2 } /// Pushes the future onto the poll queue to be polled after the component renders. diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index dbcba7548..57d7c10d4 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -249,10 +249,10 @@ impl VirtualDom { // This could be unexpected, so we might rethink this behavior later // // We *could* just panic if the suspense boundary is not found - root.provide_context(SuspenseContext::new(ScopeId(0))); + root.provide_context(Rc::new(SuspenseContext::new(ScopeId(0)))); // Unlike react, we provide a default error boundary that just renders the error as a string - root.provide_context(ErrorBoundary::new(ScopeId(0))); + root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0)))); // the root element is always given element ID 0 since it's the container for the entire tree dom.elements.insert(ElementRef::null()); @@ -296,7 +296,7 @@ impl VirtualDom { /// currently suspended. pub fn is_scope_suspended(&self, id: ScopeId) -> bool { !self.scopes[id.0] - .consume_context::() + .consume_context::>() .unwrap() .waiting_on .borrow() @@ -527,7 +527,7 @@ impl VirtualDom { // first, unload any complete suspense trees for finished_fiber in self.finished_fibers.drain(..) { let scope = &mut self.scopes[finished_fiber.0]; - let context = scope.has_context::().unwrap(); + let context = scope.has_context::>().unwrap(); self.mutations .templates @@ -565,7 +565,7 @@ impl VirtualDom { // No placeholder necessary since this is a diff if !self.collected_leaves.is_empty() { let mut boundary = self.scopes[dirty.id.0] - .consume_context::() + .consume_context::>() .unwrap(); let boundary_mut = boundary.borrow_mut(); diff --git a/packages/fermi/src/hooks/atom_root.rs b/packages/fermi/src/hooks/atom_root.rs index 267b71161..24fd70093 100644 --- a/packages/fermi/src/hooks/atom_root.rs +++ b/packages/fermi/src/hooks/atom_root.rs @@ -1,6 +1,7 @@ +use std::rc::Rc; + use crate::AtomRoot; use dioxus_core::ScopeState; -use std::rc::Rc; // Returns the atom root, initiaizing it at the root of the app if it does not exist. pub fn use_atom_root(cx: &ScopeState) -> &Rc { diff --git a/packages/hooks/src/usecontext.rs b/packages/hooks/src/usecontext.rs index 5d0f42363..a6e581fda 100644 --- a/packages/hooks/src/usecontext.rs +++ b/packages/hooks/src/usecontext.rs @@ -1,13 +1,18 @@ use dioxus_core::ScopeState; +use std::rc::Rc; -/// Consume some context in the tree -pub fn use_context(cx: &ScopeState) -> Option<&T> { - cx.use_hook(|| cx.consume_context::()).as_deref() +/// Consume some context in the tree, providing a sharable handle to the value +pub fn use_context(cx: &ScopeState) -> Option<&T> { + cx.use_hook(|| cx.consume_context::()).as_ref() } /// Provide some context via the tree and return a reference to it /// /// Once the context has been provided, it is immutable. Mutations should be done via interior mutability. -pub fn use_context_provider(cx: &ScopeState, f: impl FnOnce() -> T) -> &T { - cx.use_hook(|| cx.provide_context(f())) +pub fn use_context_provider(cx: &ScopeState, f: impl FnOnce() -> T) -> &T { + cx.use_hook(|| { + let val = f(); + cx.provide_context(val.clone()); + val + }) } diff --git a/packages/router/src/components/link.rs b/packages/router/src/components/link.rs index 1746ba5a9..4264e1ed0 100644 --- a/packages/router/src/components/link.rs +++ b/packages/router/src/components/link.rs @@ -1,6 +1,4 @@ -use std::sync::Arc; - -use crate::{use_route, RouterCore}; +use crate::{use_route, RouterContext}; use dioxus::prelude::*; /// Props for the [`Link`](struct.Link.html) component. @@ -77,7 +75,7 @@ pub struct LinkProps<'a> { /// } /// ``` pub fn Link<'a>(cx: Scope<'a, LinkProps<'a>>) -> Element { - let svc = cx.use_hook(|| cx.consume_context::>()); + let svc = use_context::(cx); let LinkProps { to, diff --git a/packages/router/src/components/route.rs b/packages/router/src/components/route.rs index 04b8944e1..60b85d687 100644 --- a/packages/router/src/components/route.rs +++ b/packages/router/src/components/route.rs @@ -1,7 +1,5 @@ +use crate::{RouteContext, RouterContext}; use dioxus::prelude::*; -use std::sync::Arc; - -use crate::{RouteContext, RouterCore}; /// Props for the [`Route`](struct.Route.html) component. #[derive(Props)] @@ -27,15 +25,13 @@ pub struct RouteProps<'a> { /// ) /// ``` pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element { - let router_root = cx - .use_hook(|| cx.consume_context::>()) - .as_ref() - .unwrap(); + let router_root = use_context::(cx).unwrap(); + let root_context = use_context::(cx); cx.use_hook(|| { // create a bigger, better, longer route if one above us exists - let total_route = match cx.consume_context::() { - Some(ctx) => ctx.total_route, + let total_route = match root_context { + Some(ctx) => ctx.total_route.clone(), None => cx.props.to.to_string(), }; @@ -46,7 +42,7 @@ pub fn Route<'a>(cx: Scope<'a, RouteProps<'a>>) -> Element { }); // submit our rout - router_root.register_total_route(route_context.total_route, cx.scope_id()); + router_root.register_total_route(route_context.total_route.clone(), cx.scope_id()); }); log::trace!("Checking Route: {:?}", cx.props.to); diff --git a/packages/router/src/components/router.rs b/packages/router/src/components/router.rs index 008197d0f..53b4717ad 100644 --- a/packages/router/src/components/router.rs +++ b/packages/router/src/components/router.rs @@ -1,6 +1,5 @@ -use crate::{cfg::RouterCfg, RouterCore}; +use crate::{cfg::RouterCfg, RouterContext, RouterService}; use dioxus::prelude::*; -use std::sync::Arc; /// The props for the [`Router`](fn.Router.html) component. #[derive(Props)] @@ -21,7 +20,7 @@ pub struct RouterProps<'a> { /// /// This lets you easily implement redirects #[props(default)] - pub onchange: EventHandler<'a, Arc>, + pub onchange: EventHandler<'a, RouterContext>, /// Set the active class of all Link components contained in this router. /// @@ -40,15 +39,15 @@ pub struct RouterProps<'a> { /// Will fallback to HashRouter is BrowserRouter is not available, or through configuration. #[allow(non_snake_case)] pub fn Router<'a>(cx: Scope<'a, RouterProps<'a>>) -> Element { - let svc = cx.use_hook(|| { - cx.provide_context(RouterCore::new( + let svc = use_context_provider(cx, || { + RouterService::new( &cx, RouterCfg { base_url: cx.props.base_url.map(|s| s.to_string()), active_class: cx.props.active_class.map(|s| s.to_string()), initial_url: cx.props.initial_url.clone(), }, - )) + ) }); // next time we run the rout_found will be filled diff --git a/packages/router/src/hooks/use_route.rs b/packages/router/src/hooks/use_route.rs index 27f99dfea..2e6b2cd4e 100644 --- a/packages/router/src/hooks/use_route.rs +++ b/packages/router/src/hooks/use_route.rs @@ -1,4 +1,4 @@ -use crate::{ParsedRoute, RouteContext, RouterCore, RouterService}; +use crate::{ParsedRoute, RouteContext, RouterContext}; use dioxus::core::{ScopeId, ScopeState}; use std::{borrow::Cow, str::FromStr, sync::Arc}; use url::Url; @@ -7,11 +7,11 @@ use url::Url; /// context of a [`Router`]. If this function is called outside of a `Router` /// component it will panic. pub fn use_route(cx: &ScopeState) -> &UseRoute { - let handle = cx.use_hook(|| { - let router = cx - .consume_context::() - .expect("Cannot call use_route outside the scope of a Router component"); + let router = cx + .consume_context::() + .expect("Cannot call use_route outside the scope of a Router component"); + let handle = cx.use_hook(|| { let route_context = cx.consume_context::(); router.subscribe_onchange(cx.scope_id()); @@ -115,7 +115,7 @@ impl UseRoute { // and reveal our cached version of UseRoute to the component. struct UseRouteListener { state: UseRoute, - router: Arc, + router: RouterContext, scope: ScopeId, } diff --git a/packages/router/src/hooks/use_router.rs b/packages/router/src/hooks/use_router.rs index 4b4a12487..bc4402ecb 100644 --- a/packages/router/src/hooks/use_router.rs +++ b/packages/router/src/hooks/use_router.rs @@ -1,10 +1,8 @@ -use crate::RouterService; -use dioxus::core::ScopeState; +use crate::RouterContext; +use dioxus::{core::ScopeState, prelude::use_context}; /// This hook provides access to the `RouterService` for the app. -pub fn use_router(cx: &ScopeState) -> &RouterService { - cx.use_hook(|| { - cx.consume_context::() - .expect("Cannot call use_route outside the scope of a Router component") - }) +pub fn use_router(cx: &ScopeState) -> &RouterContext { + use_context::(cx) + .expect("Cannot call use_route outside the scope of a Router component") } diff --git a/packages/router/src/service.rs b/packages/router/src/service.rs index 039d0033d..69ef19995 100644 --- a/packages/router/src/service.rs +++ b/packages/router/src/service.rs @@ -4,7 +4,7 @@ use crate::cfg::RouterCfg; use dioxus::core::{ScopeId, ScopeState, VirtualDom}; use std::any::Any; -use std::sync::Weak; +use std::rc::Weak; use std::{ cell::{Cell, RefCell}, collections::{HashMap, HashSet}, @@ -14,6 +14,9 @@ use std::{ }; use url::Url; +/// A clonable handle to the router +pub type RouterContext = Rc; + /// An abstraction over the platform's history API. /// /// The history is denoted using web-like semantics, with forward slashes delmitiing @@ -41,7 +44,7 @@ use url::Url; /// - On the web, this is a [`BrowserHistory`](https://docs.rs/gloo/0.3.0/gloo/history/struct.BrowserHistory.html). /// - On desktop, mobile, and SSR, this is just a Vec of Strings. Currently on /// desktop, there is no way to tap into forward/back for the app unless explicitly set. -pub struct RouterCore { +pub struct RouterService { pub(crate) route_found: Cell>, pub(crate) stack: RefCell>>, @@ -61,9 +64,6 @@ pub struct RouterCore { pub(crate) cfg: RouterCfg, } -/// A shared type for the RouterCore. -pub type RouterService = Arc; - /// A route is a combination of window title, saved state, and a URL. #[derive(Debug, Clone)] pub struct ParsedRoute { @@ -77,8 +77,8 @@ pub struct ParsedRoute { pub serialized_state: Option, } -impl RouterCore { - pub(crate) fn new(cx: &ScopeState, cfg: RouterCfg) -> Arc { +impl RouterService { + pub(crate) fn new(cx: &ScopeState, cfg: RouterCfg) -> RouterContext { #[cfg(feature = "web")] let history = Box::new(web::new()); @@ -99,7 +99,7 @@ impl RouterCore { None => Arc::new(history.init_location()), }; - let svc = Arc::new(Self { + let svc = Rc::new(Self { cfg, regen_any_route: cx.schedule_update_any(), router_id: cx.scope_id(), @@ -111,7 +111,7 @@ impl RouterCore { history, }); - svc.history.attach_listeners(Arc::downgrade(&svc)); + svc.history.attach_listeners(Rc::downgrade(&svc)); svc } @@ -247,12 +247,9 @@ impl RouterCore { /// that owns the router. /// /// This might change in the future. -pub fn get_router_from_vdom( - dom: &VirtualDom, - target_scope: Option, -) -> Option> { - dom.get_scope(target_scope.unwrap_or(ScopeId(0))) - .and_then(|scope| scope.consume_context::>()) +pub fn get_router_from_vdom(dom: &VirtualDom, target_scope: ScopeId) -> Option { + dom.get_scope(target_scope) + .and_then(|scope| scope.consume_context::()) } fn clean_route(route: String) -> String { @@ -319,7 +316,7 @@ pub(crate) trait RouterProvider { fn replace(&self, route: &ParsedRoute); fn native_location(&self) -> Box; fn init_location(&self) -> ParsedRoute; - fn attach_listeners(&self, svc: Weak); + fn attach_listeners(&self, svc: Weak); } #[cfg(not(feature = "web"))] @@ -350,7 +347,7 @@ mod hash { fn replace(&self, _route: &ParsedRoute) {} - fn attach_listeners(&self, _svc: Weak) {} + fn attach_listeners(&self, _svc: Weak) {} } } @@ -418,7 +415,7 @@ mod web { } } - fn attach_listeners(&self, svc: std::sync::Weak) { + fn attach_listeners(&self, svc: std::sync::Weak) { self._listener.set(Some(EventListener::new( &web_sys::window().unwrap(), "popstate", From 7b1cdb7d8511fa94085a888cbe548f5cd2c7b9b7 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 5 Dec 2022 16:08:41 -0800 Subject: [PATCH 07/17] feat: get desktop working with new template strategy --- packages/core/src/create.rs | 6 +-- packages/core/src/diff.rs | 2 +- packages/core/src/mutations.rs | 1 + packages/core/src/nodes.rs | 18 +++++---- packages/desktop/src/controller.rs | 5 +-- packages/desktop/src/events.rs | 1 - packages/html/src/render_template.rs | 6 +-- packages/interpreter/src/interpreter.js | 52 +++++++++++++++++++++++-- packages/rsx/src/lib.rs | 8 ++-- packages/ssr/src/cache.rs | 4 +- packages/web/src/dom.rs | 7 ++-- 11 files changed, 79 insertions(+), 31 deletions(-) diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index 71c5d2198..b41bacb7c 100644 --- a/packages/core/src/create.rs +++ b/packages/core/src/create.rs @@ -40,7 +40,7 @@ impl<'b> VirtualDom { for (root_idx, root) in template.template.roots.iter().enumerate() { // We might need to generate an ID for the root node on_stack += match root { - TemplateNode::DynamicText(id) | TemplateNode::Dynamic(id) => { + TemplateNode::DynamicText { id } | TemplateNode::Dynamic { id } => { match &template.dynamic_nodes[*id] { // a dynamic text node doesn't replace a template node, instead we create it on the fly DynamicNode::Text(VText { id: slot, value }) => { @@ -70,7 +70,7 @@ impl<'b> VirtualDom { } } - TemplateNode::Element { .. } | TemplateNode::Text(_) => { + TemplateNode::Element { .. } | TemplateNode::Text { .. } => { let this_id = self.next_root(template, root_idx); template.root_ids[root_idx].set(this_id); @@ -215,7 +215,7 @@ impl<'b> VirtualDom { .filter(|root| { matches!( root, - TemplateNode::Dynamic(_) | TemplateNode::DynamicText(_) + TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. } ) }) .count(); diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index fca6a45e0..423cf677b 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -911,7 +911,7 @@ fn matching_components<'a>( .zip(right.template.roots.iter()) .map(|(l, r)| { let (l, r) = match (l, r) { - (TemplateNode::Dynamic(l), TemplateNode::Dynamic(r)) => (l, r), + (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r), _ => return None, }; diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 3687cd956..2512ae9fa 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -13,6 +13,7 @@ use crate::{arena::ElementId, ScopeId, Template}; /// Templates, however, apply to all subtrees, not just target subtree. /// /// Mutations are the only link between the RealDOM and the VirtualDOM. +#[cfg_attr(feature = "serialize", derive(serde::Serialize))] #[derive(Debug, Default)] #[must_use = "not handling edits can lead to visual inconsistencies in UI"] pub struct Mutations<'a> { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index a7968abf5..7b6764417 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -78,8 +78,8 @@ impl<'a> VNode<'a> { /// Returns [`None`] if the root is actually a static node (Element/Text) pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> { match &self.template.roots[idx] { - TemplateNode::Element { .. } | TemplateNode::Text(_) => None, - TemplateNode::Dynamic(id) | TemplateNode::DynamicText(id) => { + TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None, + TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => { Some(&self.dynamic_nodes[*id]) } } @@ -132,7 +132,7 @@ pub struct Template<'a> { /// /// This can be created at compile time, saving the VirtualDom time when diffing the tree #[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize))] +#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))] pub enum TemplateNode<'a> { /// An statically known element in the dom. /// @@ -159,15 +159,15 @@ pub enum TemplateNode<'a> { }, /// This template node is just a piece of static text - Text(&'a str), + Text { text: &'a str }, /// This template node is unknown, and needs to be created at runtime. - Dynamic(usize), + Dynamic { id: usize }, /// This template node is known to be some text, but needs to be created at runtime /// /// This is separate from the pure Dynamic variant for various optimizations - DynamicText(usize), + DynamicText { id: usize }, } /// A node created at runtime @@ -252,7 +252,11 @@ pub struct VText<'a> { /// An attribute of the TemplateNode, created at compile time #[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr( + feature = "serialize", + derive(serde::Serialize, serde::Deserialize), + serde(tag = "type") +)] pub enum TemplateAttribute<'a> { /// This attribute is entirely known at compile time, enabling Static { diff --git a/packages/desktop/src/controller.rs b/packages/desktop/src/controller.rs index d01b6545f..a71c43b47 100644 --- a/packages/desktop/src/controller.rs +++ b/packages/desktop/src/controller.rs @@ -54,8 +54,7 @@ impl DesktopController { { let edits = dom.rebuild(); let mut queue = edit_queue.lock().unwrap(); - queue.push(serde_json::to_string(&edits.templates).unwrap()); - queue.push(serde_json::to_string(&edits.edits).unwrap()); + queue.push(serde_json::to_string(&edits).unwrap()); proxy.send_event(UserWindowEvent::EditsReady).unwrap(); } @@ -116,8 +115,6 @@ impl DesktopController { let (_id, view) = self.webviews.iter_mut().next().unwrap(); - println!("processing pending edits {:?}", new_queue.len()); - for edit in new_queue.drain(..) { view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit)) .unwrap(); diff --git a/packages/desktop/src/events.rs b/packages/desktop/src/events.rs index c407013ea..1dba8978f 100644 --- a/packages/desktop/src/events.rs +++ b/packages/desktop/src/events.rs @@ -1,6 +1,5 @@ //! Convert a serialized event to an event trigger -use dioxus_core::ElementId; use dioxus_html::events::*; use serde::{Deserialize, Serialize}; use serde_json::from_value; diff --git a/packages/html/src/render_template.rs b/packages/html/src/render_template.rs index 5fbff5ff0..e608f6aa1 100644 --- a/packages/html/src/render_template.rs +++ b/packages/html/src/render_template.rs @@ -33,9 +33,9 @@ fn render_template_node(node: &TemplateNode, out: &mut String) -> std::fmt::Resu } write!(out, "")?; } - TemplateNode::Text(t) => write!(out, "{t}")?, - TemplateNode::Dynamic(_) => write!(out, "