Skip to main content

zencodec_coroutine/
lib.rs

1//! Write a zencodec incremental decoder as straight-line `async` code.
2//!
3//! A resumable decoder ([`IncrementalDecode`]) must suspend whenever it
4//! lacks bytes and resume where it left off. Writing that by hand means a
5//! state enum per parser stage. This crate repurposes `async`/`await` as a
6//! compile-time coroutine generator instead: the codec author writes
7//! ordinary sequential code against a [`CoroutineContext`], and `rustc`
8//! synthesises the state machine. [`CoroutineDecoder`] polls it and
9//! presents the standard synchronous [`IncrementalDecode`] contract.
10//!
11//! No runtime, no executor, no threads, no `block_on`: `.await` here means
12//! "yield to the driver until bytes arrive or the sink has taken the rows".
13//! The crate is `no_std + alloc` and `forbid(unsafe_code)`.
14//!
15//! ```ignore
16//! let dec = CoroutineDecoder::new(|mut ctx: CoroutineContext| async move {
17//!     let mut hdr = [0u8; 16];
18//!     ctx.read_exact_at(0, &mut hdr).await?;
19//!     ctx.set_info(info_from(&hdr));
20//!     ctx.set_output_descriptor(PixelDescriptor::RGBA8_SRGB);
21//!     ctx.announce(MetadataUpdate::HeaderReady).await?;
22//!     for strip in strips {
23//!         let mut buf = vec![0u8; strip.len];
24//!         ctx.read_exact_at(strip.offset, &mut buf).await?;
25//!         ctx.write_rows(BatchMeta::final_rows(strip.y), &pixels(&buf)).await?;
26//!     }
27//!     Ok(OutputInfo::full_decode(w, h, PixelDescriptor::RGBA8_SRGB))
28//! });
29//! ```
30//!
31//! # Cost model
32//!
33//! Each `.await` that suspends costs one `step` round trip through the
34//! driver (no thread, no allocation). The driver owns the input; the
35//! decoder copies into its store only the offered bytes an outstanding read
36//! needs, under a byte cap, and releases them once read. Gap bytes between
37//! requested ranges are consumed but never stored, and a single read larger
38//! than the cap is an error, not a stall. Rows are copied once more, from
39//! the codec's strip into the sink. A hand-written decoder can avoid both
40//! copies by decoding from the driver's input straight into the sink, which
41//! is why bulk pixel paths are better hand-written and container walks
42//! suit this crate.
43
44#![no_std]
45#![forbid(unsafe_code)]
46#![warn(missing_docs)]
47
48extern crate alloc;
49
50mod admitted_input;
51mod context;
52mod decoder;
53mod input;
54
55pub use context::{CoroutineContext, CoroutineError};
56pub use decoder::{BoxFuture, BoxedCoroutineDecoder, CoroutineDecoder, DEFAULT_INPUT_CAP};
57
58#[doc(no_inline)]
59pub use zencodec::decode::IncrementalDecode;