diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
new file mode 100644
index 000000000..332e03eb2
--- /dev/null
+++ b/.github/FUNDING.yml
@@ -0,0 +1,4 @@
+# These are supported funding model platforms
+
+github: jkelleyrtp # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
+open_collective: dioxus-labs # Replace with a single Open Collective username
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 000000000..e31219bb6
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,36 @@
+---
+name: Bug report
+about: Create a report to help us improve Dioxus
+---
+
+**Problem**
+
+
+
+**Steps To Reproduce**
+
+Steps to reproduce the behavior:
+
+-
+-
+-
+
+**Expected behavior**
+
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+
+If applicable, add screenshots to help explain your problem.
+
+**Environment:**
+ - Dioxus version: [e.g. v0.17, `master`]
+ - Rust version: [e.g. 1.43.0, `nightly`]
+ - OS info: [e.g. MacOS]
+ - App platform: [e.g. `web`, `desktop`]
+
+**Questionnaire**
+
+- [ ] I'm interested in fixing this myself but don't know where to start
+- [ ] I would like to fix and I have a solution
+- [ ] I don't have time to fix this right now, but maybe later
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_requst.md b/.github/ISSUE_TEMPLATE/feature_requst.md
new file mode 100644
index 000000000..f8735b9a2
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_requst.md
@@ -0,0 +1,16 @@
+---
+name: Feature Request
+about: If you have any interesting advice, you can tell us.
+---
+
+## Specific Demand
+
+
+
+## Implement Suggestion
+
+
\ No newline at end of file
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 183fb282d..2b6ea5c23 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -19,13 +19,7 @@ jobs:
max-parallel: 2
fail-fast: false
matrix:
- target:
- [
- i686-pc-windows-gnu,
- i686-pc-windows-msvc,
- x86_64-pc-windows-gnu,
- x86_64-pc-windows-msvc,
- ]
+ target: [x86_64-pc-windows-gnu, x86_64-pc-windows-msvc]
cfg_release_channel: [stable]
steps:
@@ -47,12 +41,6 @@ jobs:
rustup target add ${{ matrix.target }}
shell: powershell
- - name: Add mingw32 to path for i686-gnu
- run: |
- echo "C:\msys64\mingw32\bin" >> $GITHUB_PATH
- if: matrix.target == 'i686-pc-windows-gnu' && matrix.channel == 'nightly'
- shell: bash
-
- name: Add mingw64 to path for x86_64-gnu
run: echo "C:\msys64\mingw64\bin" >> $GITHUB_PATH
if: matrix.target == 'x86_64-pc-windows-gnu' && matrix.channel == 'nightly'
diff --git a/.gitignore b/.gitignore
index 9534dc207..9df7e9f93 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
/target
+/dist
Cargo.lock
.DS_Store
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 06b624794..b056a9590 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,26 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## Unreleased
+
+### Commit Statistics
+
+
+
+ - 1 commit contributed to the release over the course of 7 calendar days.
+ - 0 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+
+
+view details
+
+ * **Uncategorized**
+ - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
+
+
## v0.1.7 (2022-01-08)
### Bug Fixes
diff --git a/Cargo.toml b/Cargo.toml
index 884513a5b..468518f5f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,26 +1,26 @@
[package]
name = "dioxus"
-version = "0.1.7"
+version = "0.1.8"
authors = ["Jonathan Kelley"]
edition = "2018"
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
-license = "MIT/Apache-2.0"
+license = "MIT OR Apache-2.0"
repository = "https://github.com/DioxusLabs/dioxus/"
homepage = "https://dioxuslabs.com"
documentation = "https://dioxuslabs.com"
keywords = ["dom", "ui", "gui", "react", "wasm"]
[dependencies]
-dioxus-core = { path = "./packages/core", version = "^0.1.7" }
-dioxus-html = { path = "./packages/html", version = "^0.1.4", optional = true }
-dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.6", optional = true }
-dioxus-hooks = { path = "./packages/hooks", version = "^0.1.6", optional = true }
+dioxus-core = { path = "./packages/core", version = "^0.1.9" }
+dioxus-html = { path = "./packages/html", version = "^0.1.6", optional = true }
+dioxus-core-macro = { path = "./packages/core-macro", version = "^0.1.7", optional = true }
+dioxus-hooks = { path = "./packages/hooks", version = "^0.1.7", optional = true }
-dioxus-web = { path = "./packages/web", version = "^0.0.4", optional = true }
-dioxus-desktop = { path = "./packages/desktop", version = "^0.1.5", optional = true }
-dioxus-ssr = { path = "./packages/ssr", version = "^0.1.2", optional = true }
+dioxus-web = { path = "./packages/web", version = "^0.0.5", optional = true }
+dioxus-desktop = { path = "./packages/desktop", version = "^0.1.6", optional = true }
+dioxus-ssr = { path = "./packages/ssr", version = "^0.1.3", optional = true }
-dioxus-router = { path = "./packages/router", version = "^0.1.0", optional = true }
+dioxus-router = { path = "./packages/router", version = "^0.1.1", optional = true }
dioxus-mobile = { path = "./packages/mobile", version = "^0.0.3", optional = true }
# dioxus-liveview = { path = "./packages/liveview", optional = true }
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 8dbc080b8..000000000
--- a/LICENSE
+++ /dev/null
@@ -1 +0,0 @@
-MIT/Apache-2
diff --git a/LICENSE-APACHE b/LICENSE-APACHE
new file mode 100644
index 000000000..1b5ec8b78
--- /dev/null
+++ b/LICENSE-APACHE
@@ -0,0 +1,176 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
diff --git a/LICENSE-MIT b/LICENSE-MIT
new file mode 100644
index 000000000..31aa79387
--- /dev/null
+++ b/LICENSE-MIT
@@ -0,0 +1,23 @@
+Permission is hereby granted, free of charge, to any
+person obtaining a copy of this software and associated
+documentation files (the "Software"), to deal in the
+Software without restriction, including without
+limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software
+is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice
+shall be included in all copies or substantial portions
+of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
+ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
+TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
+PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
+SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
+IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index 641f5a8b8..52493b119 100644
--- a/README.md
+++ b/README.md
@@ -35,11 +35,12 @@
-
+
+
@@ -113,7 +113,7 @@ cargo run --example EXAMPLE
## Dioxus 项目
-| 文件浏览器 (桌面应用) | WiFi 扫描器 (桌面应用) | Todo管理 (所有平台) | 商城系统 (SSR/liveview) |
+| 文件浏览器 (桌面应用) | WiFi 扫描器 (桌面应用) | Todo管理 (所有平台) | 商城系统 (SSR/liveview) |
| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [](https://github.com/DioxusLabs/example-projects/blob/master/file-explorer) | [](https://github.com/DioxusLabs/example-projects/blob/master/wifi-scanner) | [](https://github.com/DioxusLabs/example-projects/blob/master/todomvc) | [](https://github.com/DioxusLabs/example-projects/blob/master/ecommerce-site) |
diff --git a/packages/core-macro/CHANGELOG.md b/packages/core-macro/CHANGELOG.md
index 79cbe766f..cf1143e1e 100644
--- a/packages/core-macro/CHANGELOG.md
+++ b/packages/core-macro/CHANGELOG.md
@@ -5,6 +5,43 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## Unreleased
+
+### New Features
+
+ - flatten props attrs
+ - add "optional" flag for props
+
+### Bug Fixes
+
+ - detection of f-string formatting in components
+
+### Commit Statistics
+
+
+
+ - 9 commits contributed to the release over the course of 12 calendar days.
+ - 3 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+
+
+view details
+
+ * **Uncategorized**
+ - Merge pull request #95 from DioxusLabs/jk/filedragindrop ([`ca0dd4a`](https://github.comgit//DioxusLabs/dioxus/commit/ca0dd4aa7192d483a195d420363f39d771f3e471))
+ - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
+ - flatten props attrs ([`d237271`](https://github.comgit//DioxusLabs/dioxus/commit/d2372717bd01fcff50af0572360e3f763d4c869d))
+ - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
+ - detection of f-string formatting in components ([`90abd9c`](https://github.comgit//DioxusLabs/dioxus/commit/90abd9c9a08c165384fae9c1f7c3fd098d512c48))
+ - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
+ - Merge pull request #107 from autarch/autarch/half-assed-router ([`8d3ac3f`](https://github.comgit//DioxusLabs/dioxus/commit/8d3ac3ff148aef9d10a393eda453a11c1e882f58))
+ - Merge pull request #127 from DioxusLabs/jk/handler-tweak ([`5bce294`](https://github.comgit//DioxusLabs/dioxus/commit/5bce294a86941090660b1cfd87809a2b0b76d2ce))
+ - add "optional" flag for props ([`47bc4e4`](https://github.comgit//DioxusLabs/dioxus/commit/47bc4e4a44a7d08d4f42102d13f0766d9d6bf358))
+
+
## v0.1.6 (2022-01-08)
### Documentation
@@ -64,7 +101,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- - 138 commits contributed to the release over the course of 352 calendar days.
+ - 139 commits contributed to the release over the course of 352 calendar days.
- 123 commits where understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' where seen in commit messages
@@ -75,6 +112,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details
* **Uncategorized**
+ - Release dioxus-core-macro v0.1.6 ([`db0a5bd`](https://github.comgit//DioxusLabs/dioxus/commit/db0a5bd6ec93803cddb3c6fda4172257b9c301c6))
- Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
- component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
- Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))
diff --git a/packages/core-macro/Cargo.toml b/packages/core-macro/Cargo.toml
index ee3dc2883..eb8210923 100644
--- a/packages/core-macro/Cargo.toml
+++ b/packages/core-macro/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "dioxus-core-macro"
-version = "0.1.6"
+version = "0.1.7"
authors = ["Jonathan Kelley"]
edition = "2021"
description = "Core macro for Dioxus Virtual DOM"
diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs
index 4ad7543a2..7099311ce 100644
--- a/packages/core-macro/src/lib.rs
+++ b/packages/core-macro/src/lib.rs
@@ -81,7 +81,7 @@ pub fn derive_typed_builder(input: proc_macro::TokenStream) -> proc_macro::Token
/// // Using an "ID" associated with your data is a good idea.
/// data.into_iter().map(|(k, v)| rsx!(li { key: "{k}" "{v}" }))
/// }}
-///
+///
/// // Matching
/// {match true {
/// true => rsx!(h1 {"Top text"}),
@@ -229,18 +229,18 @@ pub fn routable_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStrea
/// #[inline_props]
/// fn app(cx: Scope, bob: String) -> Element {
/// cx.render(rsx!("hello, {bob}"))
-/// }
+/// }
///
/// // is equivalent to
///
/// #[derive(PartialEq, Props)]
/// struct AppProps {
/// bob: String,
-/// }
+/// }
///
/// fn app(cx: Scope) -> Element {
/// cx.render(rsx!("hello, {bob}"))
-/// }
+/// }
/// ```
#[proc_macro_attribute]
pub fn inline_props(_args: proc_macro::TokenStream, s: TokenStream) -> TokenStream {
diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs
index 5257022a4..4e9d449cf 100644
--- a/packages/core-macro/src/rsx/component.rs
+++ b/packages/core-macro/src/rsx/component.rs
@@ -135,11 +135,14 @@ impl ToTokens for Component {
None => quote! { None },
};
+ let fn_name = self.name.segments.last().unwrap().ident.to_string();
+
tokens.append_all(quote! {
__cx.component(
#name,
#builder,
#key_token,
+ #fn_name
)
})
}
diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md
index a0aa9f28d..563106db3 100644
--- a/packages/core/CHANGELOG.md
+++ b/packages/core/CHANGELOG.md
@@ -5,6 +5,58 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## Unreleased
+
+### Documentation
+
+ - add comments for the Handler
+
+### New Features
+
+ - add "spawn" method
+ - allow providing context to the root component
+
+### Bug Fixes
+
+ - provide_root_context on root scopes
+ - proprogation of root context
+ - allow eventhandler to derive default
+
+### Commit Statistics
+
+
+
+ - 18 commits contributed to the release over the course of 13 calendar days.
+ - 8 commits where understood as [conventional](https://www.conventionalcommits.org).
+ - 0 issues like '(#ID)' where seen in commit messages
+
+### Commit Details
+
+
+
+view details
+
+ * **Uncategorized**
+ - Merge pull request #169 from DioxusLabs/jk/router-userouter ([`3509602`](https://github.comgit//DioxusLabs/dioxus/commit/3509602c0bcd327a33bc8c95896775e24751da1a))
+ - Merge branch 'master' of github.com:DioxusLabs/dioxus ([`8899701`](https://github.comgit//DioxusLabs/dioxus/commit/88997019c5c74658bfd76344d6bb6f3565943b41))
+ - add miri stress test ([`e9792e9`](https://github.comgit//DioxusLabs/dioxus/commit/e9792e9b95521fcf0b0b4e240d7cf0397cd79b8f))
+ - add "spawn" method ([`bad4b77`](https://github.comgit//DioxusLabs/dioxus/commit/bad4b773b7bbab6a38d4f48d88d8c5a8787927ac))
+ - drop hooks before resetting bump arena ([`2e4f765`](https://github.comgit//DioxusLabs/dioxus/commit/2e4f7659327b69b74e20af59b6d5e65c699b6adb))
+ - provide_root_context on root scopes ([`d9a07dd`](https://github.comgit//DioxusLabs/dioxus/commit/d9a07ddddb45ba00e35645ac0e0ad1a4d2dd996d))
+ - Fix various typos and grammar nits ([`9e4ec43`](https://github.comgit//DioxusLabs/dioxus/commit/9e4ec43b1e78d355c56a38e4c092170b2b01b20d))
+ - Merge pull request #121 from DioxusLabs/jk/unify ([`b287a4c`](https://github.comgit//DioxusLabs/dioxus/commit/b287a4cab3eec9e6961a3f010846291f4f105747))
+ - Merge pull request #108 from DioxusLabs/jk/fstring-component-fields ([`f4132d1`](https://github.comgit//DioxusLabs/dioxus/commit/f4132d1874f7495049fac23ba0a022ac137ad74f))
+ - proprogation of root context ([`c8d528b`](https://github.comgit//DioxusLabs/dioxus/commit/c8d528b3b18e320ca0cee1542ae5fc659c1cca81))
+ - allow providing context to the root component ([`d2bd175`](https://github.comgit//DioxusLabs/dioxus/commit/d2bd1751436ef4bec554bb361ba87aea1357036a))
+ - Enable clippy ([`b6903bf`](https://github.comgit//DioxusLabs/dioxus/commit/b6903bf558bc7a3d0fe6794a137c44fca0957d11))
+ - Merge pull request #107 from autarch/autarch/half-assed-router ([`8d3ac3f`](https://github.comgit//DioxusLabs/dioxus/commit/8d3ac3ff148aef9d10a393eda453a11c1e882f58))
+ - Merge pull request #138 from mrxiaozhuox/master ([`8c7473d`](https://github.comgit//DioxusLabs/dioxus/commit/8c7473d1943dd133f388ec36116c9d8295861b97))
+ - Merge pull request #133 from mrxiaozhuox/master ([`887f69d`](https://github.comgit//DioxusLabs/dioxus/commit/887f69d5b47bdcde4fe0eab094c0cd0de23e4f3f))
+ - Don't expect all components to have a scope in ScopeArena.ensure_drop_safety ([`9b282d8`](https://github.comgit//DioxusLabs/dioxus/commit/9b282d877ba0fe2463687cde7ea899b7f8510425))
+ - add comments for the Handler ([`036a0ff`](https://github.comgit//DioxusLabs/dioxus/commit/036a0ff49a7dade0e04c9c07071a1ff49133ee24))
+ - allow eventhandler to derive default ([`e47ead5`](https://github.comgit//DioxusLabs/dioxus/commit/e47ead5347ce778935f8f2127fcb14f520eda0ce))
+
+
## v0.1.7 (2022-01-08)
### Documentation
@@ -138,7 +190,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- - 437 commits contributed to the release over the course of 358 calendar days.
+ - 438 commits contributed to the release over the course of 358 calendar days.
- 416 commits where understood as [conventional](https://www.conventionalcommits.org).
- 0 issues like '(#ID)' where seen in commit messages
@@ -149,8 +201,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
view details
* **Uncategorized**
- - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
+ - Release dioxus-core v0.1.7 ([`16d73b2`](https://github.comgit//DioxusLabs/dioxus/commit/16d73b240fc446c6f6996c19ccf52bbcda2eca79))
- add title to doc comment ([`d11f322`](https://github.comgit//DioxusLabs/dioxus/commit/d11f322f554e7dbf43b988c9cfda56498cc49872))
+ - Release dioxus-core v0.1.7, dioxus-core-macro v0.1.6, dioxus-html v0.1.4, dioxus-desktop v0.1.5, dioxus-hooks v0.1.6, dioxus-mobile v0.0.3, dioxus-router v0.1.0, dioxus-ssr v0.1.2, dioxus-web v0.0.4, dioxus v0.1.7 ([`40d1f85`](https://github.comgit//DioxusLabs/dioxus/commit/40d1f85d0c3e2c9fd23c08840cca9f459d4e4307))
- better document the `EventHandler` type ([`be9f1a5`](https://github.comgit//DioxusLabs/dioxus/commit/be9f1a52ad2b04f101397ae34482ea7394df653b))
- component pass thru events ([`c439b0a`](https://github.comgit//DioxusLabs/dioxus/commit/c439b0ac7e09f70a04262b7c29938d8c52197b76))
- Merge pull request #74 from mrxiaozhuox/master ([`47056fd`](https://github.comgit//DioxusLabs/dioxus/commit/47056fda4577bcbdaa2a6f63d82eec876e5a5aee))
diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml
index f54c6d5d9..03b378e88 100644
--- a/packages/core/Cargo.toml
+++ b/packages/core/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "dioxus-core"
-version = "0.1.7"
+version = "0.1.9"
authors = ["Jonathan Kelley"]
edition = "2018"
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
diff --git a/packages/core/README.md b/packages/core/README.md
index 6313b1415..0cb185f45 100644
--- a/packages/core/README.md
+++ b/packages/core/README.md
@@ -94,5 +94,5 @@ Dioxus deals with arenas, lifetimes, asynchronous tasks, custom allocators, pinn
If you don't want to use a crate that uses unsafe, then this crate is not for you.
-However, we are always interested in decreasing the scope of the core VirtualDom to make it easier to review. We'd be happy to welcome PRs that can eliminate unsafe code while still upholding the numerous variants required to execute certain features.
+However, we are always interested in decreasing the scope of the core VirtualDom to make it easier to review. We'd be happy to welcome PRs that can eliminate unsafe code while still upholding the numerous invariants required to execute certain features.
diff --git a/packages/core/architecture.md b/packages/core/architecture.md
index f852fda3c..87c3cf4a6 100644
--- a/packages/core/architecture.md
+++ b/packages/core/architecture.md
@@ -5,7 +5,7 @@ This document is mostly a brain-dump on how things work. A lot of this informati
Main topics covered here:
- Fiber, Concurrency, and Cooperative Scheduling
- Suspense
-- Signals
+- Signals
- Patches
- Diffing
- Const/Static structures
@@ -25,7 +25,7 @@ During diffing, the "caller" closure is updated if the props are not `static. Th
Hooks are a form of state that's slightly more finicky than structs but more extensible overall. Hooks cannot be used in conditionals, but are portable enough to run on most targets.
-The Dioxus hook model uses a Bump arena where user's data lives.
+The Dioxus hook model uses a Bump arena where user's data lives.
Initializing hooks:
- The component is created
@@ -38,7 +38,7 @@ Initializing hooks:
Running hooks:
- Each time use_hook is called, the internal hook state is fetched as &mut T
-- We are guaranteed that our &mut T is not aliasing by re-generating any &mut T dependencies
+- We are guaranteed that our &mut T is not aliasing by re-generating any &mut T dependencies
- The hook counter is incremented
@@ -62,7 +62,7 @@ The diffing engine in Dioxus expects the RealDom
Dioxus uses patches - not imperative methods - to modify the real dom. This speeds up the diffing operation and makes diffing cancelable which is useful for cooperative scheduling. In general, the RealDom trait exists so renderers can share "Node pointers" across runtime boundaries.
-There are no contractual obligations between the VirtualDOM and RealDOM. When the VirtualDOM finishes its work, it releases a Vec of Edits (patches) which the RealDOM can use to update itself.
+There are no contractual obligations between the VirtualDOM and RealDOM. When the VirtualDOM finishes its work, it releases a Vec of Edits (patches) which the RealDOM can use to update itself.
@@ -70,11 +70,11 @@ There are no contractual obligations between the VirtualDOM and RealDOM. When th
When an EventTrigger enters the queue and "progress" is called (an async function), Dioxus will get to work running scopes and diffing nodes. Scopes are run and nodes are diffed together. Dioxus records which scopes get diffed to track the progress of its work.
-While descending through the stack frame, Dioxus will query the RealDom for "time remaining." When the time runs out, Dioxus will escape the stack frame by queuing whatever work it didn't get to, and then bubbling up out of "diff_node". Dioxus will also bubble out of "diff_node" if more important work gets queued while it was descending.
+While descending through the stack frame, Dioxus will query the RealDom for "time remaining." When the time runs out, Dioxus will escape the stack frame by queuing whatever work it didn't get to, and then bubbling up out of "diff_node". Dioxus will also bubble out of "diff_node" if more important work gets queued while it was descending.
-Once bubbled out of diff_node, Dioxus will request the next idle callback and await for it to become available. The return of this callback is a "Deadline" object which Dioxus queries through the RealDom.
+Once bubbled out of diff_node, Dioxus will request the next idle callback and await for it to become available. The return of this callback is a "Deadline" object which Dioxus queries through the RealDom.
-All of this is orchestrated to keep high priority events moving through the VirtualDOM and scheduling lower-priority work around the RealDOM's animations and periodic tasks.
+All of this is orchestrated to keep high priority events moving through the VirtualDOM and scheduling lower-priority work around the RealDOM's animations and periodic tasks.
```js
// returns a "deadline" object
function idle() {
@@ -83,7 +83,7 @@ function idle() {
```
## Suspense
-In React, "suspense" is the ability render nodes outside of the traditional lifecycle. React will wait on a future to complete, and once the data is ready, will render those nodes. React's version of suspense is designed to make working with promises in components easier.
+In React, "suspense" is the ability render nodes outside of the traditional lifecycle. React will wait on a future to complete, and once the data is ready, will render those nodes. React's version of suspense is designed to make working with promises in components easier.
In Dioxus, we have similar philosophy, but the use and details of suspense is slightly different. For starters, we don't currently allow using futures in the element structure. Technically, we can allow futures - and we do with "Signals" - but the "suspense" feature itself is meant to be self-contained within a single component. This forces you to handle all the loading states within your component, instead of outside the component, keeping things a bit more containerized.
diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs
index 499d9543e..3f3629c10 100644
--- a/packages/core/src/diff.rs
+++ b/packages/core/src/diff.rs
@@ -1,33 +1,36 @@
-//! This module contains the stateful DiffState and all methods to diff VNodes, their properties, and their children.
+#![warn(clippy::pedantic)]
+#![allow(clippy::cast_possible_truncation)]
+
+//! This module contains the stateful [`DiffState`] and all methods to diff [`VNodes`], their properties, and their children.
//!
//! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
-//! of mutations for the RealDom to apply.
+//! of mutations for the [`RealDom`] to apply.
//!
//! ## Notice:
//!
//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
-//! Components, Fragments, Suspense, SubTree memoization, incremental diffing, cancellation, NodeRefs, pausing, priority
+//! Components, Fragments, Suspense, [`SubTree`] memoization, incremental diffing, cancellation, [`NodeRefs`], pausing, priority
//! scheduling, and additional batching operations.
//!
//! ## Implementation Details:
//!
//! ### IDs for elements
//! --------------------
-//! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
+//! All nodes are addressed by their IDs. The [`RealDom`] provides an imperative interface for making changes to these nodes.
//! We don't necessarily require that DOM changes happen instantly during the diffing process, so the implementor may choose
//! to batch nodes if it is more performant for their application. The element IDs are indices into the internal element
//! array. The expectation is that implementors will use the ID as an index into a Vec of real nodes, allowing for passive
-//! garbage collection as the VirtualDOM replaces old nodes.
+//! garbage collection as the [`VirtualDOM`] replaces old nodes.
//!
//! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing,
-//! we always make sure to copy over the ID. If we don't do this properly, the ElementId will be populated incorrectly
+//! we always make sure to copy over the ID. If we don't do this properly, the [`ElementId`] will be populated incorrectly
//! and brick the user's page.
//!
//! ### Fragment Support
//! --------------------
//! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments
//! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty
-//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the NodeFactory - it is
+//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the [`NodeFactory`] - it is
//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding
//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to
//! the platform.
@@ -41,13 +44,13 @@
//! into a promise-like value. React will then work on the next "ready" fiber, checking back on the previous fiber once
//! it has finished its new work. In Dioxus, we use a similar approach, but try to completely render the tree before
//! switching sub-fibers. Instead, each future is submitted into a futures-queue and the node is manually loaded later on.
-//! Due to the frequent calls to "yield_now" we can get the pure "fetch-as-you-render" behavior of React Fiber.
+//! Due to the frequent calls to [`yield_now`] we can get the pure "fetch-as-you-render" behavior of React Fiber.
//!
//! We're able to use this approach because we use placeholder nodes - futures that aren't ready still get submitted to
//! DOM, but as a placeholder.
//!
//! Right now, the "suspense" queue is intertwined with hooks. In the future, we should allow any future to drive attributes
-//! and contents, without the need for the "use_suspense" hook. In the interim, this is the quickest way to get Suspense working.
+//! and contents, without the need for the [`use_suspense`] hook. In the interim, this is the quickest way to get Suspense working.
//!
//! ## Subtree Memoization
//! -----------------------
@@ -88,254 +91,111 @@
//! More info on how to improve this diffing algorithm:
//! -
-use crate::innerlude::*;
+use crate::innerlude::{
+ AnyProps, ElementId, Mutations, ScopeArena, ScopeId, VComponent, VElement, VFragment, VNode,
+ VPlaceholder, VText,
+};
use fxhash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec};
-use DomEdit::*;
-/// Our DiffState is an iterative tree differ.
-///
-/// It uses techniques of a stack machine to allow pausing and restarting of the diff algorithm. This
-/// was originally implemented using recursive techniques, but Rust lacks the ability to call async functions recursively,
-/// meaning we could not "pause" the original diffing algorithm.
-///
-/// Instead, we use a traditional stack machine approach to diff and create new nodes. The diff algorithm periodically
-/// calls "yield_now" which allows the machine to pause and return control to the caller. The caller can then wait for
-/// the next period of idle time, preventing our diff algorithm from blocking the main thread.
-///
-/// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's
-/// stack machines all the way down!
pub(crate) struct DiffState<'bump> {
pub(crate) scopes: &'bump ScopeArena,
pub(crate) mutations: Mutations<'bump>,
- pub(crate) stack: DiffStack<'bump>,
pub(crate) force_diff: bool,
+ pub(crate) element_stack: SmallVec<[ElementId; 10]>,
+ pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
}
-impl<'bump> DiffState<'bump> {
- pub(crate) fn new(scopes: &'bump ScopeArena) -> Self {
+impl<'b> DiffState<'b> {
+ pub fn new(scopes: &'b ScopeArena) -> Self {
Self {
scopes,
mutations: Mutations::new(),
- stack: DiffStack::new(),
force_diff: false,
- }
- }
-}
-
-/// The stack instructions we use to diff and create new nodes.
-#[derive(Debug)]
-pub(crate) enum DiffInstruction<'a> {
- Diff {
- old: &'a VNode<'a>,
- new: &'a VNode<'a>,
- },
-
- Create {
- node: &'a VNode<'a>,
- },
-
- /// pushes the node elements onto the stack for use in mount
- PrepareMove {
- node: &'a VNode<'a>,
- },
-
- Mount {
- and: MountType<'a>,
- },
-
- PopScope,
- PopElement,
-}
-
-#[derive(Debug, Clone, Copy)]
-pub(crate) enum MountType<'a> {
- Absorb,
- Append,
- Replace { old: &'a VNode<'a> },
- InsertAfter { other_node: &'a VNode<'a> },
- InsertBefore { other_node: &'a VNode<'a> },
-}
-
-pub(crate) struct DiffStack<'bump> {
- pub(crate) instructions: Vec>,
- pub(crate) nodes_created_stack: SmallVec<[usize; 10]>,
- pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
- pub(crate) element_stack: SmallVec<[ElementId; 10]>,
-}
-
-impl<'bump> DiffStack<'bump> {
- fn new() -> Self {
- Self {
- instructions: Vec::with_capacity(1000),
- nodes_created_stack: smallvec![],
- scope_stack: smallvec![],
element_stack: smallvec![],
+ scope_stack: smallvec![],
}
}
- fn pop(&mut self) -> Option> {
- self.instructions.pop()
+ pub fn diff_scope(&mut self, scopeid: ScopeId) {
+ let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
+ self.scope_stack.push(scopeid);
+ let scope = self.scopes.get_scope(scopeid).unwrap();
+ self.element_stack.push(scope.container);
+
+ self.diff_node(old, new);
+
+ self.mutations.mark_dirty_scope(scopeid);
}
- fn pop_off_scope(&mut self) {
- self.scope_stack.pop();
- }
-
- pub(crate) fn push(&mut self, instruction: DiffInstruction<'bump>) {
- self.instructions.push(instruction)
- }
-
- fn create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) {
- self.nodes_created_stack.push(0);
- self.instructions.push(DiffInstruction::Mount { and });
-
- for child in children.iter().rev() {
- self.instructions
- .push(DiffInstruction::Create { node: child });
- }
- }
-
- // todo: subtrees
- // fn push_subtree(&mut self) {
- // self.nodes_created_stack.push(0);
- // self.instructions.push(DiffInstruction::Mount {
- // and: MountType::Append,
- // });
- // }
-
- fn push_nodes_created(&mut self, count: usize) {
- self.nodes_created_stack.push(count);
- }
-
- pub(crate) fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) {
- self.nodes_created_stack.push(0);
- self.instructions.push(DiffInstruction::Mount { and });
- self.instructions.push(DiffInstruction::Create { node });
- }
-
- fn add_child_count(&mut self, count: usize) {
- *self.nodes_created_stack.last_mut().unwrap() += count;
- }
-
- fn pop_nodes_created(&mut self) -> usize {
- self.nodes_created_stack.pop().unwrap()
- }
-
- fn current_scope(&self) -> Option {
- self.scope_stack.last().copied()
- }
-
- fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) {
- // Push the new scope onto the stack
- self.scope_stack.push(idx);
-
- self.instructions.push(DiffInstruction::PopScope);
-
- // Run the creation algorithm with this scope on the stack
- // ?? I think we treat components as fragments??
- self.instructions.push(DiffInstruction::Create { node });
- }
-}
-
-impl<'bump> DiffState<'bump> {
- /// Progress the diffing for this "fiber"
- ///
- /// This method implements a depth-first iterative tree traversal.
- ///
- /// We do depth-first to maintain high cache locality (nodes were originally generated recursively).
- ///
- /// Returns a `bool` indicating that the work completed properly.
- pub fn work(&mut self, mut deadline_expired: impl FnMut() -> bool) -> bool {
- while let Some(instruction) = self.stack.pop() {
- match instruction {
- DiffInstruction::Diff { old, new } => self.diff_node(old, new),
- DiffInstruction::Create { node } => self.create_node(node),
- DiffInstruction::Mount { and } => self.mount(and),
- DiffInstruction::PrepareMove { node } => {
- let num_on_stack = self.push_all_nodes(node);
- self.stack.add_child_count(num_on_stack);
+ pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) {
+ use VNode::{Component, Element, Fragment, Placeholder, Text};
+ match (old_node, new_node) {
+ // Check the most common cases first
+ // these are *actual* elements, not wrappers around lists
+ (Text(old), Text(new)) => {
+ if std::ptr::eq(old, new) {
+ return;
}
- DiffInstruction::PopScope => self.stack.pop_off_scope(),
- DiffInstruction::PopElement => {
- self.stack.element_stack.pop();
+
+ let root = old
+ .id
+ .get()
+ .expect("existing text nodes should have an ElementId");
+
+ if old.text != new.text {
+ self.mutations.set_text(new.text, root.as_u64());
}
- };
+ self.scopes.update_node(new_node, root);
- if deadline_expired() {
- log::trace!("Deadline expired before we could finish!");
- return false;
- }
- }
-
- true
- }
-
- // recursively push all the nodes of a tree onto the stack and return how many are there
- fn push_all_nodes(&mut self, node: &'bump VNode<'bump>) -> usize {
- match node {
- VNode::Text(_) | VNode::Placeholder(_) => {
- self.mutations.push_root(node.mounted_id());
- 1
+ new.id.set(Some(root));
}
- VNode::Fragment(_) | VNode::Component(_) => {
- //
- let mut added = 0;
- for child in node.children() {
- added += self.push_all_nodes(child);
+ (Placeholder(old), Placeholder(new)) => {
+ if std::ptr::eq(old, new) {
+ return;
}
- added
+
+ let root = old
+ .id
+ .get()
+ .expect("existing placeholder nodes should have an ElementId");
+
+ self.scopes.update_node(new_node, root);
+ new.id.set(Some(root));
}
- VNode::Element(el) => {
- let mut num_on_stack = 0;
- for child in el.children.iter() {
- num_on_stack += self.push_all_nodes(child);
+ (Element(old), Element(new)) => {
+ if std::ptr::eq(old, new) {
+ return;
}
- self.mutations.push_root(el.id.get().unwrap());
-
- num_on_stack + 1
+ self.diff_element_nodes(old, new, old_node, new_node);
}
+
+ // These two sets are pointers to nodes but are not actually nodes themselves
+ (Component(old), Component(new)) => {
+ if std::ptr::eq(old, new) {
+ return;
+ }
+ self.diff_component_nodes(old_node, new_node, *old, *new);
+ }
+
+ (Fragment(old), Fragment(new)) => {
+ if std::ptr::eq(old, new) {
+ return;
+ }
+ self.diff_fragment_nodes(old, new);
+ }
+
+ // Anything else is just a basic replace and create
+ (
+ Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
+ Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
+ ) => self.replace_node(old_node, new_node),
}
}
- fn mount(&mut self, and: MountType<'bump>) {
- let nodes_created = self.stack.pop_nodes_created();
- match and {
- // add the nodes from this virtual list to the parent
- // used by fragments and components
- MountType::Absorb => {
- self.stack.add_child_count(nodes_created);
- }
-
- MountType::Replace { old } => {
- self.replace_node(old, nodes_created);
- }
-
- MountType::Append => {
- self.mutations.edits.push(AppendChildren {
- many: nodes_created as u32,
- });
- }
-
- MountType::InsertAfter { other_node } => {
- let root = self.find_last_element(other_node).unwrap();
- self.mutations.insert_after(root, nodes_created as u32);
- }
-
- MountType::InsertBefore { other_node } => {
- let root = self.find_first_element_id(other_node).unwrap();
- self.mutations.insert_before(root, nodes_created as u32);
- }
- }
- }
-
- // =================================
- // Tools for creating new nodes
- // =================================
-
- fn create_node(&mut self, node: &'bump VNode<'bump>) {
+ pub fn create_node(&mut self, node: &'b VNode<'b>) -> usize {
match node {
VNode::Text(vtext) => self.create_text_node(vtext, node),
VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node),
@@ -345,24 +205,21 @@ impl<'bump> DiffState<'bump> {
}
}
- fn create_text_node(&mut self, vtext: &'bump VText<'bump>, node: &'bump VNode<'bump>) {
+ fn create_text_node(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>) -> usize {
let real_id = self.scopes.reserve_node(node);
-
- self.mutations.create_text_node(vtext.text, real_id);
- vtext.id.set(Some(real_id));
- self.stack.add_child_count(1);
+ text.id.set(Some(real_id));
+ self.mutations.create_text_node(text.text, real_id);
+ 1
}
- fn create_anchor_node(&mut self, anchor: &'bump VPlaceholder, node: &'bump VNode<'bump>) {
+ fn create_anchor_node(&mut self, anchor: &'b VPlaceholder, node: &'b VNode<'b>) -> usize {
let real_id = self.scopes.reserve_node(node);
-
- self.mutations.create_placeholder(real_id);
anchor.id.set(Some(real_id));
-
- self.stack.add_child_count(1);
+ self.mutations.create_placeholder(real_id);
+ 1
}
- fn create_element_node(&mut self, element: &'bump VElement<'bump>, node: &'bump VNode<'bump>) {
+ fn create_element_node(&mut self, element: &'b VElement<'b>, node: &'b VNode<'b>) -> usize {
let VElement {
tag: tag_name,
listeners,
@@ -372,174 +229,120 @@ impl<'bump> DiffState<'bump> {
id: dom_id,
parent: parent_id,
..
- } = element;
+ } = &element;
- // set the parent ID for event bubbling
- self.stack.instructions.push(DiffInstruction::PopElement);
+ parent_id.set(self.element_stack.last().copied());
- let parent = self.stack.element_stack.last().unwrap();
- parent_id.set(Some(*parent));
-
- // set the id of the element
let real_id = self.scopes.reserve_node(node);
- self.stack.element_stack.push(real_id);
+
dom_id.set(Some(real_id));
- self.mutations.create_element(tag_name, *namespace, real_id);
+ self.element_stack.push(real_id);
+ {
+ self.mutations.create_element(tag_name, *namespace, real_id);
- self.stack.add_child_count(1);
+ let cur_scope_id = self
+ .current_scope()
+ .expect("diffing should always have a scope");
- if let Some(cur_scope_id) = self.stack.current_scope() {
- for listener in *listeners {
+ for listener in listeners.iter() {
listener.mounted_node.set(Some(real_id));
self.mutations.new_event_listener(listener, cur_scope_id);
}
- } else {
- log::warn!("create element called with no scope on the stack - this is an error for a live dom");
- }
- for attr in *attributes {
- self.mutations.set_attribute(attr, real_id.as_u64());
- }
+ for attr in attributes.iter() {
+ self.mutations.set_attribute(attr, real_id.as_u64());
+ }
- // todo: the settext optimization
- //
- // if children.len() == 1 {
- // if let VNode::Text(vtext) = children[0] {
- // self.mutations.set_text(vtext.text, real_id.as_u64());
- // return;
- // }
- // }
-
- if !children.is_empty() {
- self.stack.create_children(children, MountType::Append);
+ if !children.is_empty() {
+ self.create_and_append_children(children);
+ }
}
+ self.element_stack.pop();
+
+ 1
}
- fn create_fragment_node(&mut self, frag: &'bump VFragment<'bump>) {
- self.stack.create_children(frag.children, MountType::Absorb);
+ fn create_fragment_node(&mut self, frag: &'b VFragment<'b>) -> usize {
+ self.create_children(frag.children)
}
- fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) {
- let parent_idx = self.stack.current_scope().unwrap();
+ fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize {
+ let parent_idx = self.current_scope().unwrap();
- // the component might already exist - if it does, we need to reuse it
- // this makes figure out when to drop the component more complicated
- let new_idx = if let Some(idx) = vcomponent.scope.get() {
- assert!(self.scopes.get_scope(idx).is_some());
- idx
- } else {
- // Insert a new scope into our component list
- let props: Box = vcomponent.props.borrow_mut().take().unwrap();
- let props: Box = unsafe { std::mem::transmute(props) };
- let new_idx = self.scopes.new_with_key(
- vcomponent.user_fc,
- props,
- Some(parent_idx),
- self.stack.element_stack.last().copied().unwrap(),
- 0,
- );
+ // ensure this scope doesn't already exist if we're trying to create it
+ debug_assert!(
+ vcomponent
+ .scope
+ .get()
+ .and_then(|f| self.scopes.get_scope(f))
+ .is_none(),
+ "component scope already exists"
+ );
- new_idx
- };
+ // Insert a new scope into our component list
+ let props: Box = vcomponent.props.borrow_mut().take().unwrap();
+ let props: Box = unsafe { std::mem::transmute(props) };
+ let new_idx = self.scopes.new_with_key(
+ vcomponent.user_fc,
+ props,
+ Some(parent_idx),
+ self.element_stack.last().copied().unwrap(),
+ 0,
+ );
// Actually initialize the caller's slot with the right address
vcomponent.scope.set(Some(new_idx));
- match vcomponent.can_memoize {
- true => {
- // todo: implement promotion logic. save us from boxing props that we don't need
- }
- false => {
- // track this component internally so we know the right drop order
- }
- }
+ log::trace!(
+ "created component \"{}\", id: {:?} parent {:?} orig: {:?}",
+ vcomponent.fn_name,
+ new_idx,
+ parent_idx,
+ vcomponent.originator
+ );
- // Run the scope for one iteration to initialize it
- self.scopes.run_scope(new_idx);
+ // if vcomponent.can_memoize {
+ // // todo: implement promotion logic. save us from boxing props that we don't need
+ // } else {
+ // // track this component internally so we know the right drop order
+ // }
- // Take the node that was just generated from running the component
- let nextnode = self.scopes.fin_head(new_idx);
- self.stack.create_component(new_idx, nextnode);
+ self.enter_scope(new_idx);
- // Finally, insert this scope as a seen node.
- self.mutations.dirty_scopes.insert(new_idx);
- }
+ let created = {
+ // Run the scope for one iteration to initialize it
+ self.scopes.run_scope(new_idx);
+ self.mutations.mark_dirty_scope(new_idx);
- // =================================
- // Tools for diffing nodes
- // =================================
+ // Take the node that was just generated from running the component
+ let nextnode = self.scopes.fin_head(new_idx);
+ self.create_node(nextnode)
+ };
- pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
- use VNode::*;
- match (old_node, new_node) {
- // Check the most common cases first
- // these are *actual* elements, not wrappers around lists
- (Text(old), Text(new)) => {
- if let Some(root) = old.id.get() {
- if old.text != new.text {
- self.mutations.set_text(new.text, root.as_u64());
- }
- self.scopes.update_node(new_node, root);
+ self.leave_scope();
- new.id.set(Some(root));
- }
- }
-
- (Placeholder(old), Placeholder(new)) => {
- if let Some(root) = old.id.get() {
- self.scopes.update_node(new_node, root);
- new.id.set(Some(root))
- }
- }
-
- (Element(old), Element(new)) => self.diff_element_nodes(old, new, old_node, new_node),
-
- // These two sets are pointers to nodes but are not actually nodes themselves
- (Component(old), Component(new)) => {
- self.diff_component_nodes(old_node, new_node, *old, *new)
- }
-
- (Fragment(old), Fragment(new)) => self.diff_fragment_nodes(old, new),
-
- // The normal pathway still works, but generates slightly weird instructions
- // This pathway ensures uses the ReplaceAll, not the InsertAfter and remove
- (Placeholder(_), Fragment(new)) => {
- self.stack
- .create_children(new.children, MountType::Replace { old: old_node });
- }
-
- // Anything else is just a basic replace and create
- (
- Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
- Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
- ) => self
- .stack
- .create_node(new_node, MountType::Replace { old: old_node }),
- }
+ created
}
fn diff_element_nodes(
&mut self,
- old: &'bump VElement<'bump>,
- new: &'bump VElement<'bump>,
- old_node: &'bump VNode<'bump>,
- new_node: &'bump VNode<'bump>,
+ old: &'b VElement<'b>,
+ new: &'b VElement<'b>,
+ old_node: &'b VNode<'b>,
+ new_node: &'b VNode<'b>,
) {
- let root = old.id.get().unwrap();
+ let root = old
+ .id
+ .get()
+ .expect("existing element nodes should have an ElementId");
// If the element type is completely different, the element needs to be re-rendered completely
// This is an optimization React makes due to how users structure their code
//
// This case is rather rare (typically only in non-keyed lists)
if new.tag != old.tag || new.namespace != old.namespace {
- // maybe make this an instruction?
- // issue is that we need the "vnode" but this method only has the velement
- self.stack.push_nodes_created(0);
- self.stack.push(DiffInstruction::Mount {
- and: MountType::Replace { old: old_node },
- });
- self.create_element_node(new, new_node);
+ self.replace_node(old_node, new_node);
return;
}
@@ -570,7 +373,7 @@ impl<'bump> DiffState<'bump> {
self.mutations.remove_attribute(attribute, root.as_u64());
}
for attribute in new.attributes {
- self.mutations.set_attribute(attribute, root.as_u64())
+ self.mutations.set_attribute(attribute, root.as_u64());
}
}
@@ -582,7 +385,7 @@ impl<'bump> DiffState<'bump> {
// We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
//
// TODO: take a more efficient path than this
- if let Some(cur_scope_id) = self.stack.current_scope() {
+ if let Some(cur_scope_id) = self.current_scope() {
if old.listeners.len() == new.listeners.len() {
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
if old_l.event != new_l.event {
@@ -604,159 +407,97 @@ impl<'bump> DiffState<'bump> {
}
}
- if old.children.is_empty() && !new.children.is_empty() {
- self.mutations.edits.push(PushRoot {
- root: root.as_u64(),
- });
- self.stack.element_stack.push(root);
- self.stack.instructions.push(DiffInstruction::PopElement);
- self.stack.create_children(new.children, MountType::Append);
- } else {
- self.stack.element_stack.push(root);
- self.stack.instructions.push(DiffInstruction::PopElement);
- self.diff_children(old.children, new.children);
- }
-
- // todo: this is for the "settext" optimization
- // it works, but i'm not sure if it's the direction we want to take right away
- // I haven't benchmarked the performance imporvemenet yet. Perhaps
- // we can make it a config?
-
- // match (old.children.len(), new.children.len()) {
- // (0, 0) => {}
- // (1, 1) => {
- // let old1 = &old.children[0];
- // let new1 = &new.children[0];
-
- // match (old1, new1) {
- // (VNode::Text(old_text), VNode::Text(new_text)) => {
- // if old_text.text != new_text.text {
- // self.mutations.set_text(new_text.text, root.as_u64());
- // }
- // }
- // (VNode::Text(_old_text), _) => {
- // self.stack.element_stack.push(root);
- // self.stack.instructions.push(DiffInstruction::PopElement);
- // self.stack.create_node(new1, MountType::Append);
- // }
- // (_, VNode::Text(new_text)) => {
- // self.remove_nodes([old1], false);
- // self.mutations.set_text(new_text.text, root.as_u64());
- // }
- // _ => {
- // self.stack.element_stack.push(root);
- // self.stack.instructions.push(DiffInstruction::PopElement);
- // self.diff_children(old.children, new.children);
- // }
- // }
- // }
- // (0, 1) => {
- // if let VNode::Text(text) = &new.children[0] {
- // self.mutations.set_text(text.text, root.as_u64());
- // } else {
- // self.stack.element_stack.push(root);
- // self.stack.instructions.push(DiffInstruction::PopElement);
- // }
- // }
- // (0, _) => {
- // self.mutations.edits.push(PushRoot {
- // root: root.as_u64(),
- // });
- // self.stack.element_stack.push(root);
- // self.stack.instructions.push(DiffInstruction::PopElement);
- // self.stack.create_children(new.children, MountType::Append);
- // }
- // (_, 0) => {
- // self.remove_nodes(old.children, false);
- // self.mutations.set_text("", root.as_u64());
- // }
- // (_, _) => {
- // self.stack.element_stack.push(root);
- // self.stack.instructions.push(DiffInstruction::PopElement);
- // self.diff_children(old.children, new.children);
- // }
- // }
+ match (old.children.len(), new.children.len()) {
+ (0, 0) => {}
+ (0, _) => {
+ let created = self.create_children(new.children);
+ self.mutations.append_children(created as u32);
+ }
+ (_, _) => self.diff_children(old.children, new.children),
+ };
}
fn diff_component_nodes(
&mut self,
- old_node: &'bump VNode<'bump>,
- new_node: &'bump VNode<'bump>,
- old: &'bump VComponent<'bump>,
- new: &'bump VComponent<'bump>,
+ old_node: &'b VNode<'b>,
+ new_node: &'b VNode<'b>,
+ old: &'b VComponent<'b>,
+ new: &'b VComponent<'b>,
) {
- let scope_addr = old.scope.get().unwrap();
- log::trace!("diff_component_nodes: {:?}", scope_addr);
+ let scope_addr = old
+ .scope
+ .get()
+ .expect("existing component nodes should have a scope");
if std::ptr::eq(old, new) {
- log::trace!("skipping component diff - component is the sames");
return;
}
// Make sure we're dealing with the same component (by function pointer)
if old.user_fc == new.user_fc {
- self.stack.scope_stack.push(scope_addr);
+ self.enter_scope(scope_addr);
+ {
+ // Make sure the new component vnode is referencing the right scope id
+ new.scope.set(Some(scope_addr));
- // Make sure the new component vnode is referencing the right scope id
- new.scope.set(Some(scope_addr));
+ // make sure the component's caller function is up to date
+ let scope = self
+ .scopes
+ .get_scope(scope_addr)
+ .unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
- // make sure the component's caller function is up to date
- let scope = self
- .scopes
- .get_scope(scope_addr)
- .unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
-
- // take the new props out regardless
- // when memoizing, push to the existing scope if memoization happens
- let new_props = new.props.borrow_mut().take().unwrap();
-
- let should_run = {
- if old.can_memoize {
- let props_are_the_same = unsafe {
- scope
- .props
- .borrow()
- .as_ref()
- .unwrap()
- .memoize(new_props.as_ref())
- };
- !props_are_the_same || self.force_diff
- } else {
- true
- }
- };
-
- if should_run {
- let _old_props = scope
+ // take the new props out regardless
+ // when memoizing, push to the existing scope if memoization happens
+ let new_props = new
.props
- .replace(unsafe { std::mem::transmute(Some(new_props)) });
+ .borrow_mut()
+ .take()
+ .expect("new component props should exist");
- // this should auto drop the previous props
- self.scopes.run_scope(scope_addr);
- self.mutations.dirty_scopes.insert(scope_addr);
+ let should_diff = {
+ if old.can_memoize {
+ // safety: we trust the implementation of "memoize"
+ let props_are_the_same = unsafe {
+ let new_ref = new_props.as_ref();
+ scope.props.borrow().as_ref().unwrap().memoize(new_ref)
+ };
+ !props_are_the_same || self.force_diff
+ } else {
+ true
+ }
+ };
- self.diff_node(
- self.scopes.wip_head(scope_addr),
- self.scopes.fin_head(scope_addr),
- );
- } else {
- log::trace!("memoized");
- // memoization has taken place
- drop(new_props);
- };
+ if should_diff {
+ let _old_props = scope
+ .props
+ .replace(unsafe { std::mem::transmute(Some(new_props)) });
- self.stack.scope_stack.pop();
+ // this should auto drop the previous props
+ self.scopes.run_scope(scope_addr);
+ self.mutations.mark_dirty_scope(scope_addr);
+
+ self.diff_node(
+ self.scopes.wip_head(scope_addr),
+ self.scopes.fin_head(scope_addr),
+ );
+ } else {
+ // memoization has taken place
+ drop(new_props);
+ };
+ }
+ self.leave_scope();
} else {
- self.stack
- .create_node(new_node, MountType::Replace { old: old_node });
+ self.replace_node(old_node, new_node);
}
}
- fn diff_fragment_nodes(&mut self, old: &'bump VFragment<'bump>, new: &'bump VFragment<'bump>) {
+ fn diff_fragment_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) {
// This is the case where options or direct vnodes might be used.
// In this case, it's faster to just skip ahead to their diff
if old.children.len() == 1 && new.children.len() == 1 {
- self.diff_node(&old.children[0], &new.children[0]);
+ if !std::ptr::eq(old, new) {
+ self.diff_node(&old.children[0], &new.children[0]);
+ }
return;
}
@@ -766,10 +507,6 @@ impl<'bump> DiffState<'bump> {
self.diff_children(old.children, new.children);
}
- // =============================================
- // Utilities for creating new diff instructions
- // =============================================
-
// Diff the given set of old and new children.
//
// The parent must be on top of the change list stack when this function is
@@ -785,11 +522,15 @@ impl<'bump> DiffState<'bump> {
//
// Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only
// to an element, and appending makes sense.
- fn diff_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+ fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+ if std::ptr::eq(old, new) {
+ return;
+ }
+
// Remember, fragments can never be empty (they always have a single child)
match (old, new) {
([], []) => {}
- ([], _) => self.stack.create_children(new, MountType::Append),
+ ([], _) => self.create_and_append_children(new),
(_, []) => self.remove_nodes(old, true),
_ => {
let new_is_keyed = new[0].key().is_some();
@@ -821,29 +562,21 @@ impl<'bump> DiffState<'bump> {
// [... parent]
//
// the change list stack is in the same state when this function returns.
- fn diff_non_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+ fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+ use std::cmp::Ordering;
+
// Handled these cases in `diff_children` before calling this function.
debug_assert!(!new.is_empty());
debug_assert!(!old.is_empty());
- for (new, old) in new.iter().zip(old.iter()).rev() {
- self.stack.push(DiffInstruction::Diff { new, old });
- }
-
- use std::cmp::Ordering;
match old.len().cmp(&new.len()) {
Ordering::Greater => self.remove_nodes(&old[new.len()..], true),
- Ordering::Less => {
- self.stack.create_children(
- &new[old.len()..],
- MountType::InsertAfter {
- other_node: old.last().unwrap(),
- },
- );
- }
- Ordering::Equal => {
- // nothing - they're the same size
- }
+ Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
+ Ordering::Equal => {}
+ }
+
+ for (new, old) in new.iter().zip(old.iter()) {
+ self.diff_node(old, new);
}
}
@@ -863,10 +596,10 @@ impl<'bump> DiffState<'bump> {
// https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
//
// The stack is empty upon entry.
- fn diff_keyed_children(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+ fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
if cfg!(debug_assertions) {
let mut keys = fxhash::FxHashSet::default();
- let mut assert_unique_keys = |children: &'bump [VNode<'bump>]| {
+ let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
keys.clear();
for child in children {
let key = child.key();
@@ -907,6 +640,7 @@ impl<'bump> DiffState<'bump> {
!((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
"keyed children must have the same number of children"
);
+
if new_middle.is_empty() {
// remove the old elements
self.remove_nodes(old_middle, true);
@@ -916,30 +650,15 @@ impl<'bump> DiffState<'bump> {
if left_offset == 0 {
// insert at the beginning of the old list
let foothold = &old[old.len() - right_offset];
- self.stack.create_children(
- new_middle,
- MountType::InsertBefore {
- other_node: foothold,
- },
- );
+ self.create_and_insert_before(new_middle, foothold);
} else if right_offset == 0 {
// insert at the end the old list
let foothold = old.last().unwrap();
- self.stack.create_children(
- new_middle,
- MountType::InsertAfter {
- other_node: foothold,
- },
- );
+ self.create_and_insert_after(new_middle, foothold);
} else {
// inserting in the middle
let foothold = &old[left_offset - 1];
- self.stack.create_children(
- new_middle,
- MountType::InsertAfter {
- other_node: foothold,
- },
- );
+ self.create_and_insert_after(new_middle, foothold);
}
} else {
self.diff_keyed_middle(old_middle, new_middle);
@@ -953,9 +672,8 @@ impl<'bump> DiffState<'bump> {
/// If there is no offset, then this function returns None and the diffing is complete.
fn diff_keyed_ends(
&mut self,
-
- old: &'bump [VNode<'bump>],
- new: &'bump [VNode<'bump>],
+ old: &'b [VNode<'b>],
+ new: &'b [VNode<'b>],
) -> Option<(usize, usize)> {
let mut left_offset = 0;
@@ -964,19 +682,14 @@ impl<'bump> DiffState<'bump> {
if old.key() != new.key() {
break;
}
- self.stack.push(DiffInstruction::Diff { old, new });
+ self.diff_node(old, new);
left_offset += 1;
}
// If that was all of the old children, then create and append the remaining
// new children and we're finished.
if left_offset == old.len() {
- self.stack.create_children(
- &new[left_offset..],
- MountType::InsertAfter {
- other_node: old.last().unwrap(),
- },
- );
+ self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
return None;
}
@@ -1014,7 +727,8 @@ impl<'bump> DiffState<'bump> {
// This function will load the appropriate nodes onto the stack and do diffing in place.
//
// Upon exit from this function, it will be restored to that same self.
- fn diff_keyed_middle(&mut self, old: &'bump [VNode<'bump>], new: &'bump [VNode<'bump>]) {
+ #[allow(clippy::too_many_lines)]
+ fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
/*
1. Map the old keys into a numerical ordering based on indices.
2. Create a map of old key to its index
@@ -1039,8 +753,8 @@ impl<'bump> DiffState<'bump> {
// 0. Debug sanity checks
// Should have already diffed the shared-key prefixes and suffixes.
- debug_assert_ne!(new.first().map(|n| n.key()), old.first().map(|o| o.key()));
- debug_assert_ne!(new.last().map(|n| n.key()), old.last().map(|o| o.key()));
+ debug_assert_ne!(new.first().map(VNode::key), old.first().map(VNode::key));
+ debug_assert_ne!(new.last().map(VNode::key), old.last().map(VNode::key));
// 1. Map the old keys into a numerical ordering based on indices.
// 2. Create a map of old key to its index
@@ -1072,10 +786,12 @@ impl<'bump> DiffState<'bump> {
if shared_keys.is_empty() {
if let Some(first_old) = old.get(0) {
self.remove_nodes(&old[1..], true);
- self.stack
- .create_children(new, MountType::Replace { old: first_old })
+ let nodes_created = self.create_children(new);
+ self.replace_inner(first_old, nodes_created);
} else {
- self.stack.create_children(new, MountType::Append {});
+ // I think this is wrong - why are we appending?
+ // only valid of the if there are no trailing elements
+ self.create_and_append_children(new);
}
return;
}
@@ -1103,33 +819,31 @@ impl<'bump> DiffState<'bump> {
lis_sequence.pop();
}
- let apply = |new_idx, new_node: &'bump VNode<'bump>, stack: &mut DiffStack<'bump>| {
- let old_index = new_index_to_old_index[new_idx];
- if old_index == u32::MAX as usize {
- stack.create_node(new_node, MountType::Absorb);
- } else {
- // this function should never take LIS indices
- stack.push(DiffInstruction::PrepareMove { node: new_node });
- stack.push(DiffInstruction::Diff {
- new: new_node,
- old: &old[old_index],
- });
- }
- };
+ for idx in &lis_sequence {
+ self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]);
+ }
- // add mount instruction for the last items not covered by the lis
- let first_lis = *lis_sequence.first().unwrap();
- if first_lis > 0 {
- self.stack.push_nodes_created(0);
- self.stack.push(DiffInstruction::Mount {
- and: MountType::InsertBefore {
- other_node: &new[first_lis],
- },
- });
+ let mut nodes_created = 0;
- for (idx, new_node) in new[..first_lis].iter().enumerate().rev() {
- apply(idx, new_node, &mut self.stack);
+ // add mount instruction for the first items not covered by the lis
+ let last = *lis_sequence.last().unwrap();
+ if last < (new.len() - 1) {
+ for (idx, new_node) in new[(last + 1)..].iter().enumerate() {
+ let new_idx = idx + last + 1;
+ let old_index = new_index_to_old_index[new_idx];
+ if old_index == u32::MAX as usize {
+ nodes_created += self.create_node(new_node);
+ } else {
+ self.diff_node(&old[old_index], new_node);
+ nodes_created += self.push_all_nodes(new_node);
+ }
}
+
+ self.mutations.insert_after(
+ self.find_last_element(&new[last]).unwrap(),
+ nodes_created as u32,
+ );
+ nodes_created = 0;
}
// for each spacing, generate a mount instruction
@@ -1137,85 +851,53 @@ impl<'bump> DiffState<'bump> {
let mut last = *lis_iter.next().unwrap();
for next in lis_iter {
if last - next > 1 {
- self.stack.push_nodes_created(0);
- self.stack.push(DiffInstruction::Mount {
- and: MountType::InsertBefore {
- other_node: &new[last],
- },
- });
- for (idx, new_node) in new[(next + 1)..last].iter().enumerate().rev() {
- apply(idx + next + 1, new_node, &mut self.stack);
+ for (idx, new_node) in new[(next + 1)..last].iter().enumerate() {
+ let new_idx = idx + next + 1;
+ let old_index = new_index_to_old_index[new_idx];
+ if old_index == u32::MAX as usize {
+ nodes_created += self.create_node(new_node);
+ } else {
+ self.diff_node(&old[old_index], new_node);
+ nodes_created += self.push_all_nodes(new_node);
+ }
}
+
+ self.mutations.insert_before(
+ self.find_first_element(&new[last]).unwrap(),
+ nodes_created as u32,
+ );
+
+ nodes_created = 0;
}
last = *next;
}
- // add mount instruction for the first items not covered by the lis
- let last = *lis_sequence.last().unwrap();
- if last < (new.len() - 1) {
- self.stack.push_nodes_created(0);
- self.stack.push(DiffInstruction::Mount {
- and: MountType::InsertAfter {
- other_node: &new[last],
- },
- });
- for (idx, new_node) in new[(last + 1)..].iter().enumerate().rev() {
- apply(idx + last + 1, new_node, &mut self.stack);
+ // add mount instruction for the last items not covered by the lis
+ let first_lis = *lis_sequence.first().unwrap();
+ if first_lis > 0 {
+ for (idx, new_node) in new[..first_lis].iter().enumerate() {
+ let old_index = new_index_to_old_index[idx];
+ if old_index == u32::MAX as usize {
+ nodes_created += self.create_node(new_node);
+ } else {
+ self.diff_node(&old[old_index], new_node);
+ nodes_created += self.push_all_nodes(new_node);
+ }
}
- }
- for idx in lis_sequence.iter().rev() {
- self.stack.push(DiffInstruction::Diff {
- new: &new[*idx],
- old: &old[new_index_to_old_index[*idx]],
- });
+ self.mutations.insert_before(
+ self.find_first_element(&new[first_lis]).unwrap(),
+ nodes_created as u32,
+ );
}
}
- // =====================
- // Utilities
- // =====================
-
- fn find_last_element(&mut self, vnode: &'bump VNode<'bump>) -> Option {
- let mut search_node = Some(vnode);
-
- loop {
- match &search_node.take().unwrap() {
- VNode::Text(t) => break t.id.get(),
- VNode::Element(t) => break t.id.get(),
- VNode::Placeholder(t) => break t.id.get(),
- VNode::Fragment(frag) => {
- search_node = frag.children.last();
- }
- VNode::Component(el) => {
- let scope_id = el.scope.get().unwrap();
- search_node = Some(self.scopes.root_node(scope_id));
- }
- }
- }
+ fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) {
+ let nodes_created = self.create_node(new);
+ self.replace_inner(old, nodes_created);
}
- fn find_first_element_id(&mut self, vnode: &'bump VNode<'bump>) -> Option {
- let mut search_node = Some(vnode);
-
- loop {
- match &search_node.take().unwrap() {
- // the ones that have a direct id
- VNode::Fragment(frag) => {
- search_node = Some(&frag.children[0]);
- }
- VNode::Component(el) => {
- let scope_id = el.scope.get().unwrap();
- search_node = Some(self.scopes.root_node(scope_id));
- }
- VNode::Text(t) => break t.id.get(),
- VNode::Element(t) => break t.id.get(),
- VNode::Placeholder(t) => break t.id.get(),
- }
- }
- }
-
- fn replace_node(&mut self, old: &'bump VNode<'bump>, nodes_created: usize) {
+ fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: usize) {
match old {
VNode::Element(el) => {
let id = old
@@ -1224,6 +906,7 @@ impl<'bump> DiffState<'bump> {
self.mutations.replace_with(id, nodes_created as u32);
self.remove_nodes(el.children, false);
+ self.scopes.collect_garbage(id);
}
VNode::Text(_) | VNode::Placeholder(_) => {
@@ -1232,35 +915,38 @@ impl<'bump> DiffState<'bump> {
.unwrap_or_else(|| panic!("broke on {:?}", old));
self.mutations.replace_with(id, nodes_created as u32);
+ self.scopes.collect_garbage(id);
}
VNode::Fragment(f) => {
- self.replace_node(&f.children[0], nodes_created);
+ self.replace_inner(&f.children[0], nodes_created);
self.remove_nodes(f.children.iter().skip(1), true);
}
VNode::Component(c) => {
- let node = self.scopes.fin_head(c.scope.get().unwrap());
- self.replace_node(node, nodes_created);
-
+ log::trace!("Replacing component {:?}", old);
let scope_id = c.scope.get().unwrap();
+ let node = self.scopes.fin_head(scope_id);
- // we can only remove components if they are actively being diffed
- if self.stack.scope_stack.contains(&c.originator) {
- self.scopes.try_remove(scope_id).unwrap();
+ self.enter_scope(scope_id);
+ {
+ self.replace_inner(node, nodes_created);
+
+ log::trace!("Replacing component x2 {:?}", old);
+
+ // we can only remove components if they are actively being diffed
+ if self.scope_stack.contains(&c.originator) {
+ log::trace!("Removing component {:?}", old);
+
+ self.scopes.try_remove(scope_id).unwrap();
+ }
}
+ self.leave_scope();
}
}
}
- /// schedules nodes for garbage collection and pushes "remove" to the mutation stack
- /// remove can happen whenever
- pub(crate) fn remove_nodes(
- &mut self,
- nodes: impl IntoIterator>,
- gen_muts: bool,
- ) {
- // or cache the vec on the diff machine
+ pub fn remove_nodes(&mut self, nodes: impl IntoIterator>, gen_muts: bool) {
for node in nodes {
match node {
VNode::Text(t) => {
@@ -1288,6 +974,8 @@ impl<'bump> DiffState<'bump> {
self.mutations.remove(id.as_u64());
}
+ self.scopes.collect_garbage(id);
+
self.remove_nodes(e.children, false);
}
@@ -1296,16 +984,124 @@ impl<'bump> DiffState<'bump> {
}
VNode::Component(c) => {
- let scope_id = c.scope.get().unwrap();
- let root = self.scopes.root_node(scope_id);
- self.remove_nodes(Some(root), gen_muts);
+ self.enter_scope(c.scope.get().unwrap());
+ {
+ let scope_id = c.scope.get().unwrap();
+ let root = self.scopes.root_node(scope_id);
+ self.remove_nodes([root], gen_muts);
- // we can only remove this node if the originator is actively
- if self.stack.scope_stack.contains(&c.originator) {
- self.scopes.try_remove(scope_id).unwrap();
+ // we can only remove this node if the originator is actively in our stackß
+ if self.scope_stack.contains(&c.originator) {
+ self.scopes.try_remove(scope_id).unwrap();
+ }
}
+ self.leave_scope();
}
}
}
}
+
+ fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize {
+ let mut created = 0;
+ for node in nodes {
+ created += self.create_node(node);
+ }
+ created
+ }
+
+ fn create_and_append_children(&mut self, nodes: &'b [VNode<'b>]) {
+ let created = self.create_children(nodes);
+ self.mutations.append_children(created as u32);
+ }
+
+ fn create_and_insert_after(&mut self, nodes: &'b [VNode<'b>], after: &'b VNode<'b>) {
+ let created = self.create_children(nodes);
+ let last = self.find_last_element(after).unwrap();
+ self.mutations.insert_after(last, created as u32);
+ }
+
+ fn create_and_insert_before(&mut self, nodes: &'b [VNode<'b>], before: &'b VNode<'b>) {
+ let created = self.create_children(nodes);
+ let first = self.find_first_element(before).unwrap();
+ self.mutations.insert_before(first, created as u32);
+ }
+
+ fn current_scope(&self) -> Option {
+ self.scope_stack.last().copied()
+ }
+
+ fn enter_scope(&mut self, scope: ScopeId) {
+ self.scope_stack.push(scope);
+ }
+
+ fn leave_scope(&mut self) {
+ self.scope_stack.pop();
+ }
+
+ fn find_last_element(&self, vnode: &'b VNode<'b>) -> Option {
+ let mut search_node = Some(vnode);
+ loop {
+ match &search_node.take().unwrap() {
+ VNode::Text(t) => break t.id.get(),
+ VNode::Element(t) => break t.id.get(),
+ VNode::Placeholder(t) => break t.id.get(),
+ VNode::Fragment(frag) => search_node = frag.children.last(),
+ VNode::Component(el) => {
+ let scope_id = el.scope.get().unwrap();
+ search_node = Some(self.scopes.root_node(scope_id));
+ }
+ }
+ }
+ }
+
+ fn find_first_element(&self, vnode: &'b VNode<'b>) -> Option {
+ let mut search_node = Some(vnode);
+ loop {
+ match &search_node.take().expect("search node to have an ID") {
+ VNode::Text(t) => break t.id.get(),
+ VNode::Element(t) => break t.id.get(),
+ VNode::Placeholder(t) => break t.id.get(),
+ VNode::Fragment(frag) => search_node = Some(&frag.children[0]),
+ VNode::Component(el) => {
+ let scope = el.scope.get().expect("element to have a scope assigned");
+ search_node = Some(self.scopes.root_node(scope));
+ }
+ }
+ }
+ }
+
+ // recursively push all the nodes of a tree onto the stack and return how many are there
+ fn push_all_nodes(&mut self, node: &'b VNode<'b>) -> usize {
+ match node {
+ VNode::Text(_) | VNode::Placeholder(_) => {
+ self.mutations.push_root(node.mounted_id());
+ 1
+ }
+
+ VNode::Fragment(frag) => {
+ let mut added = 0;
+ for child in frag.children {
+ added += self.push_all_nodes(child);
+ }
+ added
+ }
+
+ VNode::Component(c) => {
+ let scope_id = c.scope.get().unwrap();
+ let root = self.scopes.root_node(scope_id);
+ self.push_all_nodes(root)
+ }
+
+ VNode::Element(el) => {
+ let mut num_on_stack = 0;
+ for child in el.children.iter() {
+ num_on_stack += self.push_all_nodes(child);
+ }
+
+ self.mutations.push_root(el.id.get().unwrap());
+
+ num_on_stack + 1
+ }
+ }
+ }
}
diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs
index c0dad0a47..8386cedd8 100644
--- a/packages/core/src/events.rs
+++ b/packages/core/src/events.rs
@@ -18,7 +18,7 @@ impl BubbleState {
}
}
-/// User Events are events that are shuttled from the renderer into the VirtualDom trhough the scheduler channel.
+/// User Events are events that are shuttled from the renderer into the VirtualDom through the scheduler channel.
///
/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
/// where each listener is checked and fired if the event name matches.
diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs
index dba4effbb..e81ddca87 100644
--- a/packages/core/src/lib.rs
+++ b/packages/core/src/lib.rs
@@ -12,7 +12,6 @@ pub(crate) mod util;
pub(crate) mod virtual_dom;
pub(crate) mod innerlude {
- pub(crate) use crate::diff::*;
pub use crate::events::*;
pub use crate::lazynodes::*;
pub use crate::mutations::*;
diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs
index fa96edd1b..bd94d0b71 100644
--- a/packages/core/src/mutations.rs
+++ b/packages/core/src/mutations.rs
@@ -143,6 +143,10 @@ impl<'a> Mutations<'a> {
self.edits.push(InsertBefore { n, root });
}
+ pub(crate) fn append_children(&mut self, n: u32) {
+ self.edits.push(AppendChildren { many: n });
+ }
+
// Remove Nodes from the dom
pub(crate) fn remove(&mut self, id: u64) {
self.edits.push(Remove { root: id });
@@ -217,6 +221,10 @@ impl<'a> Mutations<'a> {
let name = attribute.name;
self.edits.push(RemoveAttribute { name, root });
}
+
+ pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
+ self.dirty_scopes.insert(scope);
+ }
}
// refs are only assigned once
diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs
index 80de92917..7a36f58ce 100644
--- a/packages/core/src/nodes.rs
+++ b/packages/core/src/nodes.rs
@@ -4,7 +4,7 @@
//! cheap and *very* fast to construct - building a full tree should be quick.
use crate::{
- innerlude::{Element, Properties, Scope, ScopeId, ScopeState},
+ innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState},
lazynodes::LazyNodes,
AnyEvent, Component,
};
@@ -21,7 +21,7 @@ use std::{
/// - the `rsx!` macro
/// - the [`NodeFactory`] API
pub enum VNode<'src> {
- /// Text VNodes simply bump-allocated (or static) string slices
+ /// Text VNodes are simply bump-allocated (or static) string slices
///
/// # Example
///
@@ -147,13 +147,6 @@ impl<'src> VNode<'src> {
}
}
- pub(crate) fn children(&self) -> &[VNode<'src>] {
- match &self {
- VNode::Fragment(f) => f.children,
- _ => &[],
- }
- }
-
// Create an "owned" version of the vnode.
pub fn decouple(&self) -> VNode<'src> {
match *self {
@@ -175,13 +168,21 @@ impl Debug for VNode<'_> {
.field("key", &el.key)
.field("attrs", &el.attributes)
.field("children", &el.children)
+ .field("id", &el.id)
.finish(),
VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text),
- VNode::Placeholder(_) => write!(s, "VNode::VPlaceholder"),
+ VNode::Placeholder(t) => write!(s, "VNode::VPlaceholder {{ id: {:?} }}", t.id),
VNode::Fragment(frag) => {
write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children)
}
- VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc),
+ VNode::Component(comp) => s
+ .debug_struct("VNode::VComponent")
+ .field("name", &comp.fn_name)
+ .field("fnptr", &comp.user_fc)
+ .field("key", &comp.key)
+ .field("scope", &comp.scope)
+ .field("originator", &comp.originator)
+ .finish(),
}
}
}
@@ -351,11 +352,18 @@ type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
/// }
///
/// ```
-#[derive(Default)]
pub struct EventHandler<'bump, T = ()> {
pub callback: RefCell