Skip to main content

recon_core/
child.rs

1//! A child protocol with the inbox its indications are collected into.
2//!
3//! Every composing protocol did the same eleven lines per child: take the inbox out of `self`,
4//! borrow the child, call [`Cx::with_child_consuming`], drain what was collected, put the inbox
5//! back. Constraint 4 in `CLAUDE.md` said to write that by hand two or three times before removing
6//! it; there were sixteen copies when this was written. This is the removal, and it is a struct
7//! rather than a macro so that the control flow stays in the parent's own text.
8//!
9//! The inbox is handed back **by value** rather than drained here, because the parent handles a
10//! child's indications with `&mut self` — including, sometimes, by calling `run` again on the same
11//! or another child — and a borrow held across that would not compile. [`Child::reclaim`] puts the
12//! allocation back so it is reused across events, which is what `tests/alloc_probe.rs` measures.
13
14use crate::store::{KeyedSlot, SeqSlot, Slot};
15use crate::{Cx, ProtoCx, Protocol};
16use core::convert::Infallible;
17use core::ops::{Deref, DerefMut};
18
19/// A protocol owned by another, with the inbox its indications are collected into.
20pub struct Child<P: Protocol> {
21    proto: P,
22    inbox: Vec<P::Ind>,
23}
24
25impl<P: Protocol> Child<P> {
26    pub fn new(proto: P) -> Self {
27        Child { proto, inbox: Vec::new() }
28    }
29
30    /// Replace the protocol, keeping the inbox's allocation.
31    ///
32    /// For a child that is rebuilt while running — the leader-driven consensus replaces its epoch
33    /// consensus on every epoch change.
34    pub fn replace(&mut self, proto: P) {
35        self.proto = proto;
36    }
37
38    /// Put the inbox back after the indications `run` returned have been handled.
39    pub fn reclaim(&mut self, mut inbox: Vec<P::Ind>) {
40        inbox.clear();
41        self.inbox = inbox;
42    }
43}
44
45impl<P> Child<P>
46where
47    P: Protocol<Meta = Infallible, Entry = Infallible>,
48{
49    /// Run `f` against the child, forwarding what it sends as `wrap(msg)` and returning what it
50    /// indicated for the parent to handle. Hand the returned inbox to [`Child::reclaim`] once done.
51    ///
52    /// The child is handed no store — see [`Cx::with_child_consuming`]. A child that keeps
53    /// something durably is run with [`Child::run_durable`] instead.
54    pub fn run<M, I, Me, En>(
55        &mut self,
56        cx: &mut Cx<'_, M, I, P::Note, Me, En>,
57        wrap: impl Fn(P::Msg) -> M,
58        f: impl FnOnce(&mut P, &mut ProtoCx<'_, P>),
59    ) -> Vec<P::Ind> {
60        let mut inbox = core::mem::take(&mut self.inbox);
61        let proto = &mut self.proto;
62        cx.with_child_consuming(wrap, &mut inbox, |ccx| f(proto, ccx));
63        inbox
64    }
65}
66
67impl<P> Child<P>
68where
69    P: Protocol<Entry = Infallible>,
70{
71    /// [`Child::run`], for a child that keeps durable metadata in `slot` of the parent's record.
72    ///
73    /// See [`Cx::with_durable_child_consuming`] for why the write is one and not two.
74    pub fn run_durable<M, I, Me, En>(
75        &mut self,
76        cx: &mut Cx<'_, M, I, P::Note, Me, En>,
77        wrap: impl Fn(P::Msg) -> M,
78        slot: Slot<Me, P::Meta>,
79        f: impl FnOnce(&mut P, &mut ProtoCx<'_, P>),
80    ) -> Vec<P::Ind> {
81        let mut inbox = core::mem::take(&mut self.inbox);
82        let proto = &mut self.proto;
83        cx.with_durable_child_consuming(wrap, &mut inbox, slot, |ccx| f(proto, ccx));
84        inbox
85    }
86}
87
88impl<P: Protocol> Child<P> {
89    /// [`Child::run_durable`], for one member of a family of durable children.
90    ///
91    /// See [`Cx::with_keyed_durable_child_consuming`].
92    pub fn run_keyed<M, I, Me, En, K>(
93        &mut self,
94        cx: &mut Cx<'_, M, I, P::Note, Me, En>,
95        wrap: impl Fn(P::Msg) -> M,
96        slot: KeyedSlot<Me, P::Meta, K>,
97        key: K,
98        f: impl FnOnce(&mut P, &mut ProtoCx<'_, P>),
99    ) -> Vec<P::Ind>
100    where
101        P: Protocol<Entry = Infallible>,
102    {
103        let mut inbox = core::mem::take(&mut self.inbox);
104        let proto = &mut self.proto;
105        cx.with_keyed_durable_child_consuming(wrap, &mut inbox, slot, key, |ccx| f(proto, ccx));
106        inbox
107    }
108
109    /// [`Child::run_durable`], for a child that keeps metadata **and appends**.
110    ///
111    /// See [`Cx::with_durable_child`]: the child's entries go into the parent's one sequence, so
112    /// the order between a parent's entry and its child's is real rather than invented at recovery.
113    pub fn run_appending<M, I, Me, En>(
114        &mut self,
115        cx: &mut Cx<'_, M, I, P::Note, Me, En>,
116        wrap: impl Fn(P::Msg) -> M,
117        slot: Slot<Me, P::Meta>,
118        entries: SeqSlot<En, P::Entry>,
119        f: impl FnOnce(&mut P, &mut ProtoCx<'_, P>),
120    ) -> Vec<P::Ind> {
121        let mut inbox = core::mem::take(&mut self.inbox);
122        let proto = &mut self.proto;
123        cx.with_durable_child(wrap, &mut inbox, slot, entries, |ccx| f(proto, ccx));
124        inbox
125    }
126}
127
128/// Shows the protocol. The inbox is empty between events, and its element type need not be
129/// `Debug` for the parent to be.
130impl<P: Protocol + core::fmt::Debug> core::fmt::Debug for Child<P> {
131    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
132        self.proto.fmt(f)
133    }
134}
135
136impl<P: Protocol> Deref for Child<P> {
137    type Target = P;
138    fn deref(&self) -> &P {
139        &self.proto
140    }
141}
142
143impl<P: Protocol> DerefMut for Child<P> {
144    fn deref_mut(&mut self) -> &mut P {
145        &mut self.proto
146    }
147}