Skip to content

feat(zero-rust): typed LiveView, with GPUI and Rune on it - #217

Open
tmm wants to merge 2 commits into
mainfrom
feat/zero-rust-live-view
Open

tmm wants to merge 2 commits into
mainfrom
feat/zero-rust-live-view

Conversation

@tmm

@tmm tmm commented Nov 14, 2026

Copy link
Copy Markdown
Member

tmm's decision 3(b): a typed live view, the Rust counterpart of Swift's LiveQuery (useQuery). GPUI and Rune move onto it and their copies are deleted.

let issues: LiveView<Vec<Issue>> = zero.live(&ISSUE_LIST_V2.with(args), &ttl)?;
issues.value();          // Rc<Vec<Issue>>
issues.result_type();
issues.error();          // Option<LiveError>: Query(..) or Decode(..), keeping the last good value
let remove = issues.add_listener(|rows, result_type| { /* ... */ });
  • Zero::live takes a typed Query<R, T> (default build) or a QueryRequest (serde feature), through a small typed::TypedQuery<C> trait.
  • It keeps working when Zero replaces its client; a test covers this through the public API.
  • Row reuse, as Swift's RowCache does: an update re-reads only the rows whose entries changed, and an update with no row changes reads nothing. QueryValue gains update_from_view, with a default.

Benchmarks (3 rounds, medians): an optimistic update with the view read as structs, by hand vs LiveView. Making the view costs the same either way, and the existing benchmarks against main are within noise.

Rows By hand LiveView
1,000 82.8 µs 19.1 µs
10,000 759 µs 61.6 µs
100,000 13.1 ms 0.52 ms

Apps:

  • GPUI: Live is now LiveView plus its cx.notify wake-up.
  • Rune: watch builds a LiveView per client.

Slots and error display are unchanged. One difference: rows that fail to decode on the first read now make live fail, and the app prints the error. That only happens when the generated types don't match the data, which is a bug.

Changeset: zero-rust minor (with 0.3.0).

Checked locally:

  • Rust: clippy -D warnings, tests (11 new in live_view.rs), docs, and conformance (66,384 cases, 0 failures). The default build still has no dependencies.
  • Apps: zbugs-model, zbugs-gpui (with and without e2e), and zbugs-rune pass fmt, clippy, and tests.

Zero::live(&query, &ttl) takes a typed Query or a named query's
QueryRequest (through the new typed::TypedQuery trait) and returns a
LiveView<T>, the Rust counterpart of the Swift client's LiveQuery: the
query's value read as its Rust type and kept current, with its result
type, an error (the server's query error, or rows that didn't read,
which keep the last value), listeners, update_ttl, and the underlying
MaterializedView. It follows the client when the client replaces itself,
and dropping it removes its listeners and destroys the view.

An update reads only the rows whose entries changed, as Swift's RowCache
does (QueryValue::update_from_view, with a default that reads anew), and
keeps a copy of the entries rather than sharing the view's, so the view
changes its rows in place. In turns, an optimistic update with a view of
1,000, 10,000, and 100,000 rows read as structs went from 82.8, 759, and
13,130 us (reading every row in a listener) to 19.1, 61.6, and 521 us.
zbugs-gpui's Live is now a GPUI adapter over a LiveView that only wakes
the view to notify it; its Snapshot and its own reading of the rows are
gone, and the app reads live.value(). zbugs-rune's watch makes a
LiveView per client on the sync thread and ships its value to the UI,
in place of materializing and reading the rows itself; a row that
doesn't read is still printed and not applied.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants