Skip to main content

behavior_actors/
stash.rs

1//! Pure message holding and replay composition.
2
3use std::collections::VecDeque;
4
5use behavior::{Actions, Address, Behavior, BirthMode, SendEffects, User, UserEvent};
6use behavior::{Never, Step};
7
8mod sealed {
9    pub trait StaticallyInfallible {}
10}
11
12/// Statically proven uninhabited transition error accepted by [`Stash`].
13///
14/// The trait is sealed: replay safety depends on the error being genuinely
15/// impossible, so downstream code cannot assert the capability for an
16/// inhabited type.
17pub trait StaticallyInfallible: sealed::StaticallyInfallible {}
18
19impl sealed::StaticallyInfallible for Never {}
20impl StaticallyInfallible for Never {}
21
22impl<A, M> sealed::StaticallyInfallible for crate::MachineError<A, M, Never> {}
23impl<A, M> StaticallyInfallible for crate::MachineError<A, M, Never> {}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum StashRoute {
27    Stash,
28    Deliver,
29    Release,
30}
31
32/// Semantic observation of the outermost composed stash, independent of its
33/// structural nesting depth.
34pub trait StashStatus {
35    fn stashed_messages(&self) -> usize;
36}
37
38/// FIFO message holding for an infallible inner fold.
39///
40/// Releasing one command may replay several retained mailbox inputs in one
41/// transition. Because [`behavior::Actions`] has no rollback effect, a
42/// fallible inner fold could reject after earlier replayed inputs had already
43/// produced unreturnable actions. Such an inner behavior is therefore not a
44/// valid `Stash` composition:
45///
46/// ```compile_fail,E0277
47/// struct Fallible;
48/// impl behavior::Protocol for Fallible { type Addr = behavior::MailAddr; type Msg = (); }
49/// impl behavior::Behavior for Fallible {
50///     type Protocol = Self;
51///     type Event = behavior::User<behavior::MailAddr, ()>;
52///     type Sends = Vec<behavior::Never>;
53///     type Ph = behavior::Never;
54///     type Error = u8;
55///     type Birth = behavior::NoBirths;
56///     fn transition(&mut self, _: behavior::ActiveTurn, _: Self::Event) -> behavior::BehaviorActed<Self> {
57///         Err(1)
58///     }
59/// }
60/// fn route(_: &()) -> behavior_actors::StashRoute { behavior_actors::StashRoute::Release }
61/// fn requires_behavior<B: behavior::Behavior>(_: &B) {}
62/// requires_behavior(&behavior_actors::Stash::new(Fallible, |_, message| route(message)));
63/// ```
64pub struct Stash<B: Behavior> {
65    inner: B,
66    route: fn(&B, &behavior::BehaviorMessage<B>) -> StashRoute,
67    held: VecDeque<User<behavior::BehaviorAddr<B>, behavior::BehaviorMessage<B>>>,
68}
69
70impl<B: Behavior<Ph = Never>> Stash<B> {
71    /// Wrap `inner` with `route`, a pure decision over the actual inner state and message.
72    ///
73    /// Stashed messages retain FIFO order and ownership until a later
74    /// [`StashRoute::Release`]. Each replay borrows the state returned by the
75    /// preceding inner transition; the route must perform no effect.
76    /// Construction performs no transition or runtime
77    /// operation.
78    #[must_use]
79    pub fn new(inner: B, route: fn(&B, &behavior::BehaviorMessage<B>) -> StashRoute) -> Self {
80        Self {
81            inner,
82            route,
83            held: VecDeque::new(),
84        }
85    }
86
87    #[must_use]
88    pub fn held(&self) -> usize {
89        self.held.len()
90    }
91}
92
93impl<B: Behavior<Ph = Never>> StashStatus for Stash<B> {
94    fn stashed_messages(&self) -> usize {
95        self.held()
96    }
97}
98
99impl<B> behavior::BehaviorBase for Stash<B>
100where
101    B: Behavior<Ph = Never> + behavior::BehaviorBase,
102{
103    type Base = B::Base;
104
105    fn base(&self) -> &Self::Base {
106        self.inner.base()
107    }
108}
109
110impl<B, A, Sends, Br> Stash<B>
111where
112    A: Address,
113    Sends: SendEffects + behavior::SendsFor<B::Event>,
114    Br: BirthMode,
115    B: Behavior<Ph = Never, Sends = Sends, Birth = Br>,
116    B::Error: StaticallyInfallible,
117    B::Protocol: behavior::Protocol<Addr = A>,
118{
119    fn drain_into(
120        &mut self,
121        acc: &mut Actions<behavior::BehaviorAddr<B>, Never, B::Sends, B::Birth>,
122    ) -> Result<(), B::Error> {
123        let mut batch = core::mem::take(&mut self.held);
124        while let Some(user) = batch.pop_front() {
125            match (self.route)(&self.inner, &user.message) {
126                StashRoute::Stash => self.held.push_back(user),
127                StashRoute::Deliver | StashRoute::Release => {
128                    let actions = behavior::delegate_transition(
129                        &mut self.inner,
130                        B::Event::user(user.from, user.message),
131                    )?;
132                    acc.sends.append(actions.sends);
133                    acc.creates.extend(actions.creates);
134                    if let Step::Stop(exit) = actions.become_ {
135                        self.held.extend(batch);
136                        acc.become_ = Step::Stop(exit);
137                        return Ok(());
138                    }
139                }
140            }
141        }
142        Ok(())
143    }
144}
145
146impl<B, A, Sends, Br> Behavior for Stash<B>
147where
148    A: Address,
149    Sends: SendEffects + behavior::SendsFor<B::Event>,
150    Br: BirthMode,
151    B: Behavior<Ph = Never, Sends = Sends, Birth = Br>,
152    B::Error: StaticallyInfallible,
153    B::Protocol: behavior::Protocol<Addr = A>,
154{
155    type Protocol = B::Protocol;
156    type Event = B::Event;
157    type Sends = Sends;
158    type Ph = Never;
159    type Error = B::Error;
160    type Birth = Br;
161
162    fn init(
163        &mut self,
164        _: behavior::InitializationTurn,
165    ) -> Result<Actions<A, Never, Sends, Br>, Self::Error> {
166        behavior::initialize(&mut self.inner)
167    }
168
169    fn transition(
170        &mut self,
171        _: behavior::ActiveTurn,
172        event: B::Event,
173    ) -> Result<Actions<A, Never, Sends, Br>, Self::Error> {
174        let user = match event.into_user() {
175            Ok(user) => user,
176            Err(other) => return behavior::delegate_transition(&mut self.inner, other),
177        };
178        match (self.route)(&self.inner, &user.message) {
179            StashRoute::Stash => {
180                self.held.push_back(user);
181                Ok(Actions::cont())
182            }
183            StashRoute::Deliver => behavior::delegate_transition(
184                &mut self.inner,
185                B::Event::user(user.from, user.message),
186            ),
187            StashRoute::Release => {
188                let mut actions = behavior::delegate_transition(
189                    &mut self.inner,
190                    B::Event::user(user.from, user.message),
191                )?;
192                if !matches!(actions.become_, Step::Stop(_)) {
193                    self.drain_into(&mut actions)?;
194                }
195                Ok(actions)
196            }
197        }
198    }
199}