Skip to content

Commit

Permalink
Reactivity v3! (Part 1) 🎉 (#612)
Browse files Browse the repository at this point in the history
* scopes

* signal get and set

* signals owned by scopes

* memos and reactive graph update propagation

* improve performance by reusing allocations in topo_sort

* add remaining reactive hooks

* add some docs and reexport everything at top-level

* add a lot more docs

* add docs to everything and impl Debug, Display

* add context api

* migrate signal and memo tests over

* separate effects from memos and defer effect callbacks

* migrate all effect tests

* check if effect is alive when calling it rather than removing links during drop

* fix a few memo tests migrated from effects

* some perf optimizations around slotmap indexing

* migrate over map_keyed and map_indexed

* remove unnecessary Rc

* make Accessor take ref instead of move

* add effect batching

* Update some docs to new reactivity

Co-authored-by: Genna Wingert <wingertge@gmail.com>

* initial store API implementation

* wip: add derive macro for State and macro for parsing lens path

* add StateTrigger trait and make get! take a Store<T>

* Implement set! macro.

* fix doc comment

* Rename store2.rs to store.rs

* Satisfy clippy

* Split scope into a new file

* Move tests into scope.rs as well

* Remove explicit cx: Scope

* Set global root when propagating updates

* Simplify Root internals

* Fix CI?

* Update Cargo.toml for new crates

* Fix RUSTFLAGS

* Fix RUSTFLAGS attempt 2

* Fix conditional compilation

* Remove examples folder from sycamore-reactive3

* Do not run all tests

* Ignore doctest in lib.rs

---------

Co-authored-by: Genna Wingert <wingertge@gmail.com>
  • Loading branch information
lukechu10 and wingertge authored Sep 14, 2023
1 parent ddf95d8 commit 3ce37fb
Show file tree
Hide file tree
Showing 22 changed files with 3,227 additions and 58 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build_website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- name: Install trunk
run: >
wget -qO-
https://github.com/thedodd/trunk/releases/download/v0.14.0/trunk-x86_64-unknown-linux-gnu.tar.gz
https://github.com/thedodd/trunk/releases/download/v0.16.0/trunk-x86_64-unknown-linux-gnu.tar.gz
| tar -xzf- && sudo mv trunk /usr/bin/
- name: Cargo generate-lockfile
Expand Down
18 changes: 11 additions & 7 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,23 +48,27 @@ jobs:
target/
key: ${{ runner.os }}-cargo-${{ matrix.rust }}-${{ hashFiles('**/Cargo.lock') }}

- name: Run tests with default features
- name: Run tests for packages/sycamore with default features
# Only run tests for sycamore to prevent stray feature flags from other tests.
run: cd packages/sycamore && cargo test

- name: Run tests with all features
- name: Run tests (with UI tests) on 1.72.0
if: matrix.rust == '1.72.0'
env:
RUN_UI_TESTS: true
run: cargo test --all-features
run: cargo test

- name: Run tests on stable
if: matrix.rust == 'stable'
run: cargo test

- name: Run tests with all features excluding UI
if: matrix.rust != '1.72.0'
- name: Run all tests on nightly
if: matrix.rust == 'nightly'
run: cargo test --all-features

- name: Run headless browser tests
- name: Run headless browser tests on nightly
run: cd packages/sycamore && wasm-pack test --firefox --chrome --headless --all-features
if: always()
if: matrix.rust == 'nightly'

clippy:
name: Clippy
Expand Down
77 changes: 41 additions & 36 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,43 +1,48 @@
[workspace]
resolver = "2"
members = [
# Packages
"packages/sycamore",
"packages/sycamore-core",
"packages/sycamore-futures",
"packages/sycamore-macro",
"packages/sycamore-reactive",
"packages/sycamore-router",
"packages/sycamore-router-macro",
"packages/sycamore-web",
# Packages
"packages/sycamore",
"packages/sycamore-core",
"packages/sycamore-futures",
"packages/sycamore-macro",
"packages/sycamore-reactive",
"packages/sycamore-reactive3",
"packages/sycamore-reactive-macro",
"packages/sycamore-router",
"packages/sycamore-router-macro",
"packages/sycamore-web",

# Examples
"examples/components",
"examples/context",
"examples/counter",
"examples/http-request",
"examples/http-request-builder",
"examples/hydrate",
"examples/iteration",
"examples/js-framework-benchmark",
"examples/hello-builder",
"examples/hello-world",
"examples/higher-order-components",
"examples/motion",
"examples/number-binding",
"examples/ssr",
"examples/svg",
"examples/timer",
"examples/todomvc",
"examples/transitions",
"examples/attributes-passthrough",
"examples/js-snippets",
# Examples
"examples/components",
"examples/context",
"examples/counter",
"examples/http-request",
"examples/http-request-builder",
"examples/hydrate",
"examples/iteration",
"examples/js-framework-benchmark",
"examples/hello-builder",
"examples/hello-world",
"examples/higher-order-components",
"examples/motion",
"examples/number-binding",
"examples/ssr",
"examples/svg",
"examples/timer",
"examples/todomvc",
"examples/transitions",
"examples/attributes-passthrough",
"examples/js-snippets",

# Website
"docs",
"website",
# Website
"docs",
"website",

# Tools
"packages/tools/bench",
"packages/tools/bench-diff",
# Tools
"packages/tools/bench",
"packages/tools/bench-diff",
]

[profile.bench]
debug = true
35 changes: 35 additions & 0 deletions docs/next/advanced/advanced_reactivity.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,38 @@
TODO

Help us out by writing the docs and sending us a PR!

## Batching Updates

Sycamore's fine grained reactivity system updates immediately when a signal changes. This works great
in most cases because only things that depend on that change will actually be rerun. But what if
you need to make changes to two or more related signals?

### The `batch` function

The batch function lets you execute a closure and delay any effects until it completes. This means
you can update multiple related signals and only have their dependent effects run once.

Not only can this improve performance, but can even improve safety in your code since updating
related signals synchronously can cause your effects to run with an unintended state. Batching
the calls means you only ever run your effects when you're done with your mutations. You can think
of batching a little bit like database transactions.

### Example

In this example, we are assigning both names on a button click and this triggers our rendered update
twice. Using batch lets us avoid that.

```rust
let update_names = || {
batch(|| {
first_name.set_fn(|first_name| first_name + "n");
last_name.set_fn(|last_name| last_name + "!");
});
}
create_effect(cx, || {
// This would run twice when `update_names` is called without batching. With batching, it only
// runs once.
format!("{} {}", first_name.get(), last_name.get());
});
```
4 changes: 2 additions & 2 deletions docs/next/basics/control_flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ let visible = create_signal(cx, true);

view! { cx,
div {
(if *visible.get() {
(if visible.get() {
view! { cx, "Now you see me" }
} else {
view! { cx, } // Now you don't
Expand All @@ -32,7 +32,7 @@ let is_empty = create_selector(cx, || !name.get().is_empty());

view! { cx,
h1 {
(if *is_empty.get() {
(if is_empty.get() {
view! { cx, span { (name.get()) } }
} else {
view! { cx, span { "World" } }
Expand Down
17 changes: 6 additions & 11 deletions docs/next/basics/reactivity.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,7 @@ Reactivity is based on reactive primitives. A `Signal` is one such example of a
At it's simplest, a `Signal` is simply a wrapper around a type that can be read and written to and
which can be listened on whenever its wrapped value is mutated.

To create a signal, we use `create_signal(cx, ...)`. Note that the return value of this method is
not actually `Signal` but `&Signal`. The reason for this is because the created signal is allocated
on the reactive scope and therefore has its lifetime tied with the scope. Furthermore, this allows
using Rust's lifetime system to make sure signals are not accessed once its enclosing scope has been
destroyed.

To create a signal, we use `create_signal(cx, ...)`.
Here is an example of creating a signal, accessing it via `.get()`, and modifying it via
`.set(...)`.

Expand All @@ -51,7 +46,7 @@ accomplished like so:

```rust
let state = create_signal(cx, 0);
create_effect(cx, || println!("The state changed. New value: {}", state.get()));
create_effect(cx, move || println!("The state changed. New value: {}", state.get()));
// Prints "The state changed. New value: 0"
// (note that the effect is always executed at least 1 regardless of state changes)

Expand All @@ -75,11 +70,11 @@ In fact, we can easily create a derived state (also know as derive stores) using

```rust
let state = create_signal(cx, 0);
let double = create_memo(cx, || *state.get() * 2);
let double = create_memo(cx, || state.get() * 2);

assert_eq!(*double.get(), 0);
assert_eq!(double.get(), 0);
state.set(1);
assert_eq!(*double.get(), 2);
assert_eq!(double.get(), 2);
```

`create_memo(...)` automatically recomputes the derived value when any of its dependencies change.
Expand Down Expand Up @@ -109,7 +104,7 @@ let state = create_signal(cx, 0);
let text = G::text(String::new() /* placeholder */);
create_effect(cx, move || {
// Update text when `state` changes.
text.update_text(Some(&state.get()));
text.update_text(Some(&state.get(),to_string()));
});
element.append(&text);
element
Expand Down
2 changes: 1 addition & 1 deletion examples/http-request-builder/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,5 @@ fn main() {
console_error_panic_hook::set_once();
console_log::init_with_level(log::Level::Debug).unwrap();

sycamore::render(|cx| App(cx));
sycamore::render(App);
}
19 changes: 19 additions & 0 deletions packages/sycamore-reactive-macro/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "sycamore-reactive-macro"
categories = ["gui", "wasm", "web-programming"]
description = "Reactive primitives for Sycamore"
edition = "2021"
homepage = "https://github.com/sycamore-rs/sycamore"
keywords = ["wasm", "gui", "reactive"]
license = "MIT"
readme = "../../README.md"
repository = "https://github.com/sycamore-rs/sycamore"
version = "0.9.0-beta.1"

[lib]
proc-macro = true

[dependencies]
proc-macro2 = "1.0.60"
syn = "2.0.18"
quote = "1.0.28"
Loading

0 comments on commit 3ce37fb

Please sign in to comment.