Skip to main content

Module block

Module block 

Source
Expand description

Complete, contiguous sample blocks and a fill-side builder.

Block is deliberately a payload, not a queue. Compose it with the transport whose overload policy matches the application:

  • EventBuf<Block<T, N>, Q> queues up to Q complete blocks and rejects the newest block when full;
  • LatestBuf<Block<T, N>> retains only the latest complete block (decision D3 composition).

Publication cannot expose a partial block because BlockBuilder only yields a Block after all N samples have been written. Dropping or clearing a partially filled builder publishes nothing.

Timestamps are payload policy: use a timestamped sample type for T when each sample needs a stamp. The transport does not impose one.

§Known limitation: sequence-span aliasing

The contiguity check compares u32 sequence values and nothing else, and the successor skips reserved 0, so sequence identity is modular over the 2^32 - 1 nonzero span — the same counter-width boundary the transports disclose. A partial builder held while upstream omits exactly one whole span of sequences (or any whole multiple) sees the recurring value as the expected successor and completes the block as “contiguous” despite ~4.29 billion omitted samples; the gap-rejection promise (F2) is exact only below one span. Reachability: the omission must span 2^32 - 1 sequences while the same partial builder stays live — ~71.6 minutes of outage at a sustained 1 MHz sample rate, ~5 days at 10 kHz. The chosen policy is to keep the sequence one word and disclose the bound rather than carry a wider epoch: recovery from outages is the application’s job — clear() the builder when your staleness watchdog or link-layer detects a gap it cannot bound below one span.

§Costs and integration (measured; bound by decisions D3 and P)

RAM is multiple complete blocks, always. The latest composition (LatestBuf<Block<T, N>>) holds three block slots plus this private builder — 136–8,280 bytes of combined channel + builder RAM across the measured 2/8/16-byte × N = 8/32/128 grid. The queued composition stores Q blocks plus the builder. The cost is fixed in size but lives wherever you place the value: a const-constructed static lands in .bss (no flash image, no startup copy); a local consumes stack, and at up to 8,280 bytes per combined shape that is a real stack budget, not a rounding error. It is not small either way: state the number for your shape and charge it to the right budget.

Small windows can invert the economics. Continuous block release beats N individual sample publications for every measured 2-byte row and for 8/16-byte samples at N >= 32, but costs 54% / 31% more at the 8/16-byte N = 8 corners. For tiny windows, per-sample publication through a plain channel may be the cheaper shape.

Publication cost scales with block bytes — 150–8,651 reference instructions across the measured grid — and rejection is within 2–25 instructions of acceptance, because the complete rejected block is preserved and returned rather than reduced to a scalar error. Budget rejection like acceptance, not like error plumbing.

DMA integrations: the double copy is currently unavoidable in ISR context. A DMA engine has already written the samples once, and this builder’s storage is deliberately private — the public API offers no address or writable slice a DMA controller could target — so filling the builder from the DMA buffer crosses the payload a second time. Either budget both copies against the accepted row for your shape, or publish from task context where the copy is off the interrupt path. A direct-to-granted-slot fill API is exactly the registered reopening condition of cycle decision S (the deferred SlotPool foundation) — a real adopter with this requirement reopens that lane rather than prying the builder open. (DMA cache maintenance remains outside this crate, per the taxonomy’s out-of-scope list.)

No partial block is ever visible — sample-level freshness inside a filling window is unobtainable by design, stated here so it is chosen, not discovered.

The measured rows behind these numbers live in docs/proposals/block-buf-measurements.md and the joint composition matrix; the decision record is docs/records/block-buf.md.

§Example

use ph_eventing::{BlockBuilder, EventBuf};

let mut fill = BlockBuilder::<i16, 4>::new();
for (sequence, sample) in [(10, 1), (11, 2), (12, 3)] {
    assert!(fill.push(sequence, sample).expect("contiguous").is_none());
}
let block = fill.push(13, 4).expect("contiguous").expect("complete");

let queue = EventBuf::<_, 2>::new();
let producer = queue.try_producer().expect("producer");
let consumer = queue.try_consumer().expect("consumer");
// Backpressure is returned, never unwrapped: a full queue hands the
// complete block back through `Err` for the caller's policy.
assert!(producer.push(block).is_ok());
assert_eq!(consumer.pop().expect("one block queued").samples(), &[1, 2, 3, 4]);

A zero-sized block is rejected at compile time:

use ph_eventing::BlockBuilder;
const BAD: BlockBuilder<u8, 0> = BlockBuilder::new();

Structs§

Block
A complete, contiguous block of N samples.
BlockBuilder
Privately fills one block and publishes it to the caller only when complete.

Enums§

FillError
Why a sample could not be appended to a BlockBuilder.