Expand description
Freshness-first SPSC snapshot channel.
LatestBuf retains at most one unread publication. A producer always
publishes the newest complete T; if an older unread value is displaced,
PublishReport::replaced_unread reports it. The consumer takes only the
latest value and receives its generation plus the exact number skipped.
The channel uses three slots with exclusive ownership: one producer slot, one consumer slot, and one slot named by an atomic exchange state. An endpoint accesses a slot only after acquiring it through a single atomic swap. Producer and consumer therefore never touch the same payload bytes concurrently, unlike a seqlock.
Endpoint cursors live in the channel rather than the handles. Handles are stateless and dropping/reacquiring one continues its slot ownership, generation sequence, and skipped accounting.
An empty consumer poll first Acquire-loads the ready bit and returns without
an atomic read-modify-write. A pending poll still performs the same AcqRel
swap that transfers slot ownership. The private initial role indices are
encoded as zero so a const-initialized channel lands in .bss rather than
carrying its three payload slots in the flash-backed .data image.
§Decision status
Decision D1 (wrap-ambiguity policy) is closed as documented
approximation plus payload escape hatch (contract §9, non-promise X6):
skipped counts are exact while fewer than u32::MAX non-zero
generations separate successful takes, and beyond that full wrap span
the u32 result is a documented under-count —
LatestItem::skipped carries the full disclosure.
Decision D2 (Source<T> policy) is closed: Consumer does not
implement crate::Source, because try_pop cannot report the
displacement that is this channel’s designed overload behaviour —
crate::LatestSource is the consumer’s designed contract surface
(contract §9, non-promise X7), and the absent impl is pinned by a
compile_fail doctest on Consumer.
Decision D3 (first deliverable form) is closed: T stays generic
by decision, not default — a complete block is a payload
(LatestBuf<Block<T, N>> via the BlockBuf composition),
sample-versus-block is release scheduling and RAM, and no separate
latest-block type exists (contract §9).
Review caveat A.3 (handle-state continuation) is closed the same way this implementation works: role state is channel-resident in role-owned storage and handles are stateless, so a drop-and-reacquire continues by construction (contract H4; proposal Appendix A.3). Contract non-promise X8 states the role-recovery boundary: the role is held until the handle is dropped, handle lifetime is an application property, and there is deliberately no out-of-band role reset.
Every LatestBuf decision (D1–D3, A.3) is closed; the contract §9 and proposal Appendix A.3 carry the records.
§Example
use ph_eventing::LatestBuf;
let channel = LatestBuf::<u32>::new();
let producer = channel.try_producer().expect("producer");
let consumer = channel.try_consumer().expect("consumer");
assert!(!producer.publish(10).replaced_unread);
assert!(producer.publish(20).replaced_unread);
let item = consumer.take_latest().expect("latest value");
assert_eq!((item.value, item.generation, item.skipped), (20, 2, 1));Structs§
- Consumer
- Unique, stateless read handle for a
LatestBuf. - Latest
Buf - Three-slot freshness-first SPSC snapshot channel.
- Latest
Item - A value claimed from a
LatestBuf. - Producer
- Unique, stateless write handle for a
LatestBuf. - Publish
Report - Result of one successful latest-value publication.