Skip to main content

recon_core/
effect.rs

1//! The vocabulary through which a protocol affects the world.
2
3use crate::NodeId;
4use core::time::Duration;
5
6/// Everything a protocol is able to do.
7///
8/// A protocol expresses every outward action as one of these. It does not transmit, deliver,
9/// or schedule by any other means — which is what allows the same protocol to run under a
10/// simulator and under a real driver without knowing the difference.
11///
12/// The two type parameters are the protocol's own message and indication types. A timer needs
13/// none: it is named by an opaque [`TimerId`], and the driver hands the expiry back to whoever
14/// registered it rather than deducing the owner from the token's type.
15///
16/// Storage is not here: an effect is deferred, and a write must be durable before it returns.
17/// See [`crate::store`].
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub enum Effect<M, I> {
20    /// Transmit `msg` to `to`. Best-effort: the layer below may lose it.
21    Send { to: NodeId, msg: M },
22    /// Raise an indication to the layer above — the protocol delivering on its guarantee.
23    Indicate(I),
24    /// Request that `id` be handed back after `after` has elapsed.
25    SetTimer { after: Duration, id: TimerId },
26}
27
28/// Names one registered timer.
29///
30/// Opaque, and the same type for every protocol, so a timer's identity says nothing about which
31/// layer registered it or where that layer sits in a composition. The alternative — a timer type
32/// per protocol, re-wrapped by each parent — makes the *type* encode the composition path, so
33/// inserting a layer rewraps every timer beneath it and a layer's timer vocabulary becomes visible
34/// to everything above it.
35///
36/// Holding one also lets a layer recognise an expiry it has superseded, and would let one be
37/// cancelled. A `bool` can express neither.
38#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub struct TimerId(pub u64);
40
41/// Which kind of write happened, for the trace.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum WriteKind {
44    /// The metadata value was replaced.
45    Set,
46    /// An entry was appended.
47    Append,
48}
49
50impl<M, I> Effect<M, I> {
51    /// Rewrite this effect's parts, for a parent translating a child's effect into its own terms.
52    ///
53    /// This is the composition primitive: a parent re-wraps rather than re-encodes, so a
54    /// message crossing layers accumulates type structure but is never serialised twice. A timer
55    /// passes through untouched, having nothing in it that belongs to one layer.
56    pub fn map<M2, I2>(
57        self,
58        msg: impl FnOnce(M) -> M2,
59        ind: impl FnOnce(I) -> I2,
60    ) -> Effect<M2, I2> {
61        match self {
62            Effect::Send { to, msg: m } => Effect::Send { to, msg: msg(m) },
63            Effect::Indicate(i) => Effect::Indicate(ind(i)),
64            Effect::SetTimer { after, id } => Effect::SetTimer { after, id },
65        }
66    }
67}