pub struct Cx<'a, M, I, N, Me, En> { /* private fields */ }Expand description
A protocol’s window onto the world.
Supplies the current time and a seeded randomness source, and receives every effect the
protocol emits. Nothing else reaches a protocol: given the same state, event, now, and RNG
stream, it behaves identically every time.
Implementations§
Source§impl<'a, M, I, N, Me, En> Cx<'a, M, I, N, Me, En>
impl<'a, M, I, N, Me, En> Cx<'a, M, I, N, Me, En>
Sourcepub fn new(
sink: &'a mut dyn EffectSink<M, I>,
now: Time,
rng: &'a mut dyn RngCore,
store: &'a mut dyn Store<Me, En>,
next_timer: &'a mut u64,
notes: &'a mut dyn NoteSink<N>,
) -> Self
pub fn new( sink: &'a mut dyn EffectSink<M, I>, now: Time, rng: &'a mut dyn RngCore, store: &'a mut dyn Store<Me, En>, next_timer: &'a mut u64, notes: &'a mut dyn NoteSink<N>, ) -> Self
Build a context over any sink, any store, and any audience for what it narrates.
Pass NoNotes when nothing is listening, which is the ordinary case. It is a parameter
rather than an option so that the protocol’s own code is the same either way — which is
what makes narrating unable to change the run.
Sourcepub fn set_timer(&mut self, after: Duration) -> TimerId
pub fn set_timer(&mut self, after: Duration) -> TimerId
Register a timer for after, and take a handle naming it.
The handle is what a later expiry is compared against, so a protocol can tell the timer it is waiting on from one it has superseded. It says nothing about which protocol registered it or where that protocol sits in a composition.
Sourcepub fn note(&mut self, note: N)
pub fn note(&mut self, note: N)
Record a decision this protocol has taken.
Recorded at the point of the call, not deferred like an effect: a note says what has already happened, and its place in the run is where the handler put it.
Worth narrating only where the record of effects cannot say it. A note beside
indicate restating the same thing adds nothing a reader could not already see and can
drift from it. What no trace can hold is a decision that produced no effect — a message
refused, a timestamp already passed, an announcement not made — and that is the case whose
absence has cost this project the most.
Uncallable for a protocol whose Note is uninhabited: there is no value to pass. The
absence is checked rather than trusted, exactly as it is for a scope event.
struct Quiet;
impl Protocol for Quiet {
type Cmd = ();
type Ind = ();
type Msg = ();
type Scope = core::convert::Infallible;
// Narrates nothing, and so cannot.
type Note = core::convert::Infallible;
type Meta = core::convert::Infallible;
type Entry = core::convert::Infallible;
fn on_cmd(&mut self, (): (), cx: &mut ProtoCx<'_, Self>) {
cx.note(());
}
fn on_msg(&mut self, _: NodeId, (): (), _: &mut ProtoCx<'_, Self>) {}
fn on_timer(&mut self, _: TimerId, _: &mut ProtoCx<'_, Self>) {}
}Sourcepub fn storage(&mut self) -> &mut dyn Store<Me, En>
pub fn storage(&mut self) -> &mut dyn Store<Me, En>
This protocol’s durable state. A write does not return until it would survive a crash, so
a protocol may record a promise and then make it. See crate::store.
Sourcepub fn now(&self) -> Time
pub fn now(&self) -> Time
The current time — virtual under simulation, real under a live driver.
Sourcepub fn rng(&mut self) -> &mut dyn RngCore
pub fn rng(&mut self) -> &mut dyn RngCore
The seeded randomness source. Reproducible for a given seed.
Sourcepub fn with_child<CM, CI>(
&mut self,
msg: impl Fn(CM) -> M,
ind: impl Fn(CI) -> I,
f: impl FnOnce(&mut Cx<'_, CM, CI, N, Infallible, Infallible>),
)
pub fn with_child<CM, CI>( &mut self, msg: impl Fn(CM) -> M, ind: impl Fn(CI) -> I, f: impl FnOnce(&mut Cx<'_, CM, CI, N, Infallible, Infallible>), )
Run f against a child’s context, translating everything it emits into this protocol’s
terms on the way out.
The mappers are normally enum variant constructors, so a wrong one is a type error.
The child is handed a store it cannot write to, so only a child keeping nothing durably can
be composed. See crate::store::NoStore.
Nothing is buffered: a child’s effect is re-wrapped as it is emitted and passed straight
on to this context’s sink.
Sourcepub fn with_child_consuming<CM, CI>(
&mut self,
msg: impl Fn(CM) -> M,
collected: &mut Vec<CI>,
f: impl FnOnce(&mut Cx<'_, CM, CI, N, Infallible, Infallible>),
)
pub fn with_child_consuming<CM, CI>( &mut self, msg: impl Fn(CM) -> M, collected: &mut Vec<CI>, f: impl FnOnce(&mut Cx<'_, CM, CI, N, Infallible, Infallible>), )
Run f against a child’s context, forwarding what it sends and schedules but collecting
its indications into collected for this protocol to handle itself.
This is the usual shape. A child’s indication is the parent’s input — the stubborn link
reporting a delivery is what the perfect link deduplicates — so it must be consumed, not
passed through. collected belongs to the caller and is reused across events.
Sourcepub fn with_durable_child_consuming<CM, CI, CMe>(
&mut self,
msg: impl Fn(CM) -> M,
collected: &mut Vec<CI>,
slot: Slot<Me, CMe>,
f: impl FnOnce(&mut Cx<'_, CM, CI, N, CMe, Infallible>),
)
pub fn with_durable_child_consuming<CM, CI, CMe>( &mut self, msg: impl Fn(CM) -> M, collected: &mut Vec<CI>, slot: Slot<Me, CMe>, f: impl FnOnce(&mut Cx<'_, CM, CI, N, CMe, Infallible>), )
Cx::with_child_consuming, for a child that keeps durable state of its own.
The child is handed a view of slot — the part of this protocol’s record that belongs to
it. Its get projects, and its set reads this record back, replaces the child’s part, and
writes the whole thing down again. That is one write, not two, which is what stops a
crash landing between a parent’s record and its child’s.
Prefer Cx::with_child_consuming wherever the child keeps nothing: it hands a
NoStore, and a child that cannot write is one fewer thing to reason about. This exists
because Algorithm 5.10 needs it — a protocol that keeps (ets, ℓ, decision) of its own and
composes two children that each keep a record too, and whose recovery reads its children’s
records by name.
The child’s Entry is uninhabited: a child composed this way cannot append. That is the
right default and most durable children want nothing else; one that does append is
composed with Cx::with_durable_child and a SeqSlot as well.
Sourcepub fn with_keyed_durable_child_consuming<CM, CI, CMe, K>(
&mut self,
msg: impl Fn(CM) -> M,
collected: &mut Vec<CI>,
slot: KeyedSlot<Me, CMe, K>,
key: K,
f: impl FnOnce(&mut Cx<'_, CM, CI, N, CMe, Infallible>),
)
pub fn with_keyed_durable_child_consuming<CM, CI, CMe, K>( &mut self, msg: impl Fn(CM) -> M, collected: &mut Vec<CI>, slot: KeyedSlot<Me, CMe, K>, key: K, f: impl FnOnce(&mut Cx<'_, CM, CI, N, CMe, Infallible>), )
Cx::with_durable_child_consuming, for one member of a family of durable children.
A Slot names a fixed place, which is right when a parent has one child of a kind. A
parent holding one instance per round needs a place per round, and key supplies it — as
data rather than as something the slot captured, so the slot is still one fixed function.
The parent owns the keyspace: two members handed the same key share a record.
Sourcepub fn with_durable_child<CM, CI, CMe, CEn>(
&mut self,
msg: impl Fn(CM) -> M,
collected: &mut Vec<CI>,
slot: Slot<Me, CMe>,
entries: SeqSlot<En, CEn>,
f: impl FnOnce(&mut Cx<'_, CM, CI, N, CMe, CEn>),
)
pub fn with_durable_child<CM, CI, CMe, CEn>( &mut self, msg: impl Fn(CM) -> M, collected: &mut Vec<CI>, slot: Slot<Me, CMe>, entries: SeqSlot<En, CEn>, f: impl FnOnce(&mut Cx<'_, CM, CI, N, CMe, CEn>), )
Cx::with_durable_child_consuming, for a child that keeps metadata and appends.
The child’s record lives in slot of this protocol’s, exactly as before, and its entries go
into this protocol’s sequence through entries — one sequence, not two. Two sequences
would have no order between them, and a recovery replaying both would be inventing one.
The positions the child reads back are this protocol’s, and therefore sparse: its third entry
may sit at position seven. SeqSlot says why that is all a cursor needs.
Cx::with_durable_child_consuming remains the one to reach for. A child that cannot append
is one fewer thing to reason about, and its signature says it cannot; this exists because the
fail-recovery total-order broadcast keeps a durable record of its own and composes a child
that appends, which nothing did before.