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}