Skip to main content

behavior/
compose.rs

1//! Intent-facing typestate composition. Every method immediately builds a
2//! concrete pure behavior; there is no separate intent representation.
3
4use std::time::Duration;
5
6use tokio::time::Instant;
7
8use crate::behavior::{Address, Behavior, BirthMode, Births};
9use crate::next::Never;
10use crate::protocol::TimerId;
11use crate::shutdown::{FinalizeOnShutdown, ShutdownReaction, StopOnShutdown};
12use crate::stash::{Stash, StashRoute};
13use crate::supervision::{RestartPolicy, Strategy, SupervisionFailureReaction, Supervisor};
14use crate::timing::{Deadline, DeadlineReaction};
15use crate::timing::{ReceiveTimeout, ReceiveTimeoutReaction};
16use crate::watch::{LinkReaction, Watch};
17use crate::{Actions, Handler, Machine, Move, Pure, SendAlgebra};
18
19const DEFAULT_STRATEGY: Strategy = Strategy::OneForOne;
20const DEFAULT_POLICY: RestartPolicy = RestartPolicy::Transient;
21const DEFAULT_BUDGET: (u32, Duration) = (1, Duration::from_secs(5));
22
23fn identity_nonce<N: From<u64>>(index: usize) -> N {
24    N::from(u64::try_from(index).expect("fleet index fits u64"))
25}
26
27pub struct Compose<B> {
28    behavior: B,
29    next_timer: u64,
30}
31
32impl<S: Handler<O, Br, E>, O, Br: BirthMode, E> Compose<Pure<S, O, Br, E>> {
33    #[must_use]
34    pub fn new(state: S) -> Self {
35        Self {
36            behavior: Pure::new(state),
37            next_timer: 0,
38        }
39    }
40}
41
42impl<A, S, M, P, E> Compose<Machine<A, S, M, P, E>>
43where
44    A: Address,
45    P: Copy + PartialEq,
46{
47    #[must_use]
48    pub fn machine(state: S, phase: P, on: fn(P, &mut S, &M) -> Result<Move<P>, E>) -> Self {
49        Self {
50            behavior: Machine::new(state, phase, on),
51            next_timer: 0,
52        }
53    }
54}
55
56impl<B: Behavior> Compose<B> {
57    fn map_behavior<Mapped>(self, map: impl FnOnce(B) -> Mapped) -> Compose<Mapped> {
58        Compose {
59            behavior: map(self.behavior),
60            next_timer: self.next_timer,
61        }
62    }
63
64    fn map_timer<Mapped>(self, map: impl FnOnce(B, TimerId) -> Mapped) -> Compose<Mapped> {
65        let timer = TimerId(self.next_timer);
66        Compose {
67            behavior: map(self.behavior, timer),
68            next_timer: self
69                .next_timer
70                .checked_add(1)
71                .expect("timer identity exhausted"),
72        }
73    }
74
75    #[must_use]
76    pub fn from_behavior(behavior: B) -> Self {
77        Self {
78            behavior,
79            next_timer: 0,
80        }
81    }
82
83    #[must_use]
84    pub fn build(self) -> B {
85        self.behavior
86    }
87
88    #[must_use]
89    pub fn behavior(&self) -> &B {
90        &self.behavior
91    }
92
93    /// Stop normally when a typed shutdown request is folded.
94    #[must_use]
95    pub fn stop_on_shutdown(self) -> Compose<StopOnShutdown<B>> {
96        self.map_behavior(StopOnShutdown::new)
97    }
98
99    /// Apply one final pure fold, retain its sends and creations, and stop
100    /// normally regardless of the fold's become verdict.
101    #[must_use]
102    pub fn finalize_on_shutdown(
103        self,
104        finalize: ShutdownReaction<B>,
105    ) -> Compose<FinalizeOnShutdown<B>> {
106        self.map_behavior(|behavior| FinalizeOnShutdown::new(behavior, finalize))
107    }
108
109    /// Observe a peer and apply a pure reaction when it stops.
110    #[must_use]
111    pub fn watch(self, peer: B::Addr, on_stopped: LinkReaction<B>) -> Compose<Watch<B>> {
112        self.map_behavior(|behavior| Watch::new(behavior, peer, on_stopped))
113    }
114
115    /// Apply a pure reaction when the given absolute time is reached.
116    ///
117    /// # Panics
118    ///
119    /// Panics if one specification composes more than `u64::MAX` timer
120    /// capabilities.
121    #[must_use]
122    pub fn deadline(
123        self,
124        when: Option<Instant>,
125        on_reached: DeadlineReaction<B>,
126    ) -> Compose<Deadline<B>> {
127        self.map_timer(|behavior, timer| Deadline::new(behavior, timer, when, on_reached))
128    }
129
130    /// Notify the behavior once after an idle period containing no successful
131    /// user communication.
132    ///
133    /// Initialization and each successful continuing user fold emit a relative
134    /// schedule. Service events never reset inactivity. A matching delivery is
135    /// consumed before `on_elapsed` runs, and a continuing reaction remains
136    /// unarmed until another successful continuing user communication.
137    ///
138    /// # Panics
139    ///
140    /// Panics if one specification composes more than `u64::MAX` timer
141    /// capabilities.
142    #[must_use]
143    pub fn receive_timeout(
144        self,
145        after: Duration,
146        on_elapsed: ReceiveTimeoutReaction<B>,
147    ) -> Compose<ReceiveTimeout<B>> {
148        self.map_timer(|behavior, timer| ReceiveTimeout::new(behavior, timer, after, on_elapsed))
149    }
150
151    /// Hold messages selected by `route` and replay them on `Release`.
152    #[must_use]
153    pub fn stash(self, route: fn(&B::Msg) -> StashRoute) -> Compose<Stash<B>>
154    where
155        B: Behavior<Ph = Never>,
156    {
157        self.map_behavior(|behavior| Stash::new(behavior, route))
158    }
159
160    /// Create a supervised child topology. Concrete proxy and monitor types
161    /// remain hidden in the returned typestate.
162    #[must_use]
163    pub fn children<C>(self, fleet: (usize, fn(usize) -> C)) -> Compose<Supervisor<B, C>>
164    where
165        B: Behavior<Birth = Births<C>>,
166        C: Behavior<Ph = Never, Addr = B::Addr>,
167        <B::Addr as Address>::Nonce: From<u64>,
168    {
169        self.children_with_nonces(identity_nonce, fleet.0, fleet.1)
170    }
171
172    #[must_use]
173    pub fn children_with_nonces<C>(
174        self,
175        nonces: fn(usize) -> <B::Addr as Address>::Nonce,
176        count: usize,
177        build: fn(usize) -> C,
178    ) -> Compose<Supervisor<B, C>>
179    where
180        B: Behavior<Birth = Births<C>>,
181        C: Behavior<Ph = Never, Addr = B::Addr>,
182    {
183        self.map_behavior(|behavior| {
184            Supervisor::new(
185                behavior,
186                nonces,
187                count,
188                build,
189                DEFAULT_STRATEGY,
190                DEFAULT_POLICY,
191                DEFAULT_BUDGET.0,
192                DEFAULT_BUDGET.1,
193            )
194        })
195    }
196}
197
198impl<B, C> Compose<Supervisor<B, C>>
199where
200    B: Behavior<Birth = Births<C>>,
201    C: Behavior<Ph = Never, Addr = B::Addr>,
202{
203    #[must_use]
204    pub fn restart(self, strategy: Strategy) -> Self {
205        Self {
206            behavior: self.behavior.with_strategy(strategy),
207            next_timer: self.next_timer,
208        }
209    }
210
211    #[must_use]
212    pub fn when(self, policy: RestartPolicy) -> Self {
213        Self {
214            behavior: self.behavior.with_policy(policy),
215            next_timer: self.next_timer,
216        }
217    }
218
219    #[must_use]
220    pub fn within(self, maximum: u32, window: Duration) -> Self {
221        Self {
222            behavior: self.behavior.with_budget(maximum, window),
223            next_timer: self.next_timer,
224        }
225    }
226
227    /// Apply a pure reaction when supervision can no longer preserve its
228    /// child topology.
229    #[must_use]
230    pub fn on_supervision_failure(self, reaction: SupervisionFailureReaction<B>) -> Self {
231        Self {
232            behavior: self.behavior.with_failure_reaction(reaction),
233            next_timer: self.next_timer,
234        }
235    }
236}
237
238impl<B, A, Ph, Sends, Br> Behavior for Compose<B>
239where
240    A: Address,
241    Sends: SendAlgebra,
242    Br: BirthMode,
243    B: Behavior<Addr = A, Ph = Ph, Sends = Sends, Birth = Br>,
244{
245    type Addr = A;
246    type Msg = B::Msg;
247    type Event = B::Event;
248    type Sends = Sends;
249    type Ph = Ph;
250    type Error = B::Error;
251    type Birth = Br;
252
253    fn init(&mut self) -> Result<Actions<A, Ph, Sends, Br>, B::Error> {
254        self.behavior.init()
255    }
256
257    fn transition(&mut self, event: B::Event) -> Result<Actions<A, Ph, Sends, Br>, B::Error> {
258        self.behavior.transition(event)
259    }
260}