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}