oxedyne/fe2o3/fe2o3_austenite/src/delta.rs
4.8 KiB, 1 run
created by r1870400018:58772, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | //! The changed-only page delta, for the browser live view. |
| 2 | //! |
| 3 | //! Daimond's live view rerenders on every keystroke. Returning the full set of per-page SVG strings each |
| 4 | //! compile costs the consumer a fresh parse of every page and costs the wasm heap the memory of the whole |
| 5 | //! rendered document -- the very memory the Austenite swap exists to save. This layer returns instead only |
| 6 | //! the pages whose rendered SVG has changed since the consumer's last-known set, keyed by a stable page |
| 7 | //! identity so the consumer caches the rest: |
| 8 | //! |
| 9 | //! ```text |
| 10 | //! { version, order: [id, ...], changed: [{ id, svg }, ...], reset } |
| 11 | //! ``` |
| 12 | //! |
| 13 | //! * `order` is the full per-compile sequence of page ids. The consumer renders in this order, keeping a |
| 14 | //! cache keyed by id and dropping any id absent from `order`. |
| 15 | //! * `changed` carries the SVG of only those pages whose id is new this compile (not in the consumer's |
| 16 | //! prior set), so a page the consumer already holds is never resent. |
| 17 | //! * `version` is a monotonic counter, stepped each compile, for the consumer to order deltas by. |
| 18 | //! * `reset` is true on the first compile and any compile made against an empty prior set: `changed` then |
| 19 | //! carries every page in `order`, since the consumer holds nothing to reuse. |
| 20 | //! |
| 21 | //! **The identity is a content hash of the WHOLE page frame** -- body and furniture (running head, folio) |
| 22 | //! alike -- so two compiles yield the same id for a page exactly when it renders to the same SVG bytes. |
| 23 | //! This is deliberately keyed on *content*, not on the page's ordinal position: inserting a page near the |
| 24 | //! front shifts every later page's position but leaves each later page's content-hash unchanged, so only |
| 25 | //! the inserted page is resent. A position-keyed cache would resend every page from the insertion point on, |
| 26 | //! because each would fall at a slot that last held a different page. It is a superset of the emit memo's |
| 27 | //! [`page_key`](crate::emit::svg), which hashes the body alone because the memo always redraws the |
| 28 | //! furniture fresh; the delta needs the folio in the key, since the consumer reuses the whole SVG by id and |
| 29 | //! a page whose only change is its printed folio genuinely renders differently. |
| 30 | //! |
| 31 | //! **Residency.** The prior id set is supplied by the consumer on each compile (its `known` set, the ids it |
| 32 | //! still holds in its own cache) and returned as the new `order`; the compiler retains none of it between |
| 33 | //! compiles. The rendered SVG of a page is emitted into `changed` when its id is new and then dropped, never |
| 34 | //! retained. So the wasm heap holds roughly one page's SVG plus one id per page during a compile, and |
| 35 | //! nothing between compiles -- not the whole rendered document, and not the prior set. Keeping the prior set |
| 36 | //! in the compiler would also be wrong, not merely heavier: a consumer that cleared its cache while the one |
| 37 | //! singleton compiler lived on would be told nothing changed against a cache holding nothing. |
| 38 | |
| 39 | use crate::emit::svg; |
| 40 | use crate::page::Page; |
| 41 | |
| 42 | use oxedyne_fe2o3_core::prelude::*; |
| 43 | |
| 44 | use std::collections::HashSet; |
| 45 | |
| 46 | /// A changed-only compile result: the per-compile page-id sequence, the SVG of only the pages whose id is |
| 47 | /// new since the prior set, the monotonic version and whether this was a full (reset) send. |
| 48 | pub struct PageDelta { |
| 49 | pub version: u32, |
| 50 | pub order: Vec<u64>, // every page's content id, in reading order |
| 51 | pub changed: Vec<(u64, String)>, // (id, svg) for each id new this compile, rendered once |
| 52 | pub reset: bool, // true when the prior set was empty, so `changed` carries all of `order` |
| 53 | } |
| 54 | |
| 55 | /// Computes the delta between this compile's `pages` and the consumer's `prior` id set (its last `order`, |
| 56 | /// empty on the first compile). Each page's id is its whole-frame content hash; a page whose id is not in |
| 57 | /// `prior` is rendered and carried in `changed`, a page whose id is already known is left for the consumer |
| 58 | /// to reuse. An id is rendered at most once per compile, so two byte-identical pages (which share an id) |
| 59 | /// send one SVG and appear twice in `order` -- the consumer resolves both slots to the one cached page. |
| 60 | /// |
| 61 | /// `prior_version` is the version the caller last returned; the result steps it by one. |
| 62 | pub fn compute(pages: &[Page], prior: &[u64], prior_version: u32) -> Outcome<PageDelta> { |
| 63 | let reset = prior.is_empty(); |
| 64 | let known: HashSet<u64> = prior.iter().copied().collect(); |
| 65 | let mut order = Vec::with_capacity(pages.len()); |
| 66 | let mut changed = Vec::new(); |
| 67 | // The ids rendered so far this compile, so a repeated page is emitted once though `order` names it twice. |
| 68 | let mut sent: HashSet<u64> = HashSet::new(); |
| 69 | for page in pages { |
| 70 | let id = svg::page_id(page); |
| 71 | order.push(id); |
| 72 | if !known.contains(&id) && sent.insert(id) { |
| 73 | let rendered = res!(svg::render_page(page)); |
| 74 | changed.push((id, rendered)); |
| 75 | } |
| 76 | } |
| 77 | Ok(PageDelta { |
| 78 | version: prior_version.wrapping_add(1), |
| 79 | order, |
| 80 | changed, |
| 81 | reset, |
| 82 | }) |
| 83 | } |