Skip to main content

behavior_actors/time/
one_shot.rs

1//! Pure relative one-shot timer composition.
2
3use std::time::Duration;
4
5use super::domain::{TimerAdmission, TimerLease};
6use super::event::{TimedEvent, TimedReaction};
7use crate::protocol::{ScheduleAfter, TimerId};
8use behavior::Step;
9use behavior::{
10    Actions, Address, Behavior, BehaviorActed, BirthMode, EventLayer, InterpreterRequests,
11    SendEffects, SendLayer,
12};
13
14/// Infallible fold invoked for the one accepted timer generation.
15///
16/// ```compile_fail,E0308
17/// # struct App;
18/// # impl behavior::Protocol for App { type Addr = behavior::MailAddr; type Msg = (); }
19/// # impl behavior::Behavior for App {
20/// #   type Protocol = Self; type Event = behavior::User<behavior::MailAddr, ()>; type Sends = Vec<behavior::Never>;
21/// #   type Ph = behavior::Never; type Error = behavior::Never; type Birth = behavior::NoBirths;
22/// #   fn transition(&mut self, _: behavior::ActiveTurn, _: Self::Event) -> behavior::BehaviorActed<Self> { Ok(behavior::Actions::cont()) }
23/// # }
24/// fn fallible(_: &mut App) -> behavior::BehaviorActed<App> { Ok(behavior::Actions::cont()) }
25/// let _ = behavior_actors::OneShot::new(App, behavior_actors::TimerId(1), std::time::Duration::ZERO, fallible);
26/// ```
27/// Notify a wrapped behavior once after a relative delay.
28///
29/// Initialization first preserves the wrapped initialization actions, then
30/// emits one generation-tagged `ScheduleAfter` request. A matching elapsed
31/// event is consumed exactly once and folds `on_elapsed`; stale and duplicate
32/// generations are inert unless the wrapped event sum independently accepts
33/// them. Reaction sends, creations, and termination are preserved.
34/// Reactions are infallible because they receive mutable access to the wrapped
35/// behavior; ordinary delegated transitions retain the wrapped error type.
36/// The timer never rearms. Generation exhaustion disables scheduling without
37/// affecting the wrapped fold. These timer and ordering rules are Bombay
38/// policy; sleeping and clock interpretation belong to `bombay-timers`.
39pub struct OneShot<B: Behavior> {
40    inner: B,
41    id: TimerId,
42    after: Duration,
43    lease: TimerLease,
44    on_elapsed: TimedReaction<B>,
45}
46
47impl<B: Behavior> OneShot<B> {
48    /// Construct a relative one-shot wrapper definition.
49    #[must_use]
50    pub fn new(inner: B, id: TimerId, after: Duration, on_elapsed: TimedReaction<B>) -> Self {
51        Self {
52            inner,
53            id,
54            after,
55            lease: TimerLease::new(),
56            on_elapsed,
57        }
58    }
59
60    fn schedule(&mut self) -> InterpreterRequests<ScheduleAfter> {
61        self.lease
62            .arm()
63            .map_or_else(InterpreterRequests::empty, |generation| {
64                InterpreterRequests::one(ScheduleAfter::new(self.id, generation, self.after))
65            })
66    }
67
68    fn wrap(
69        actions: Actions<behavior::BehaviorAddr<B>, B::Ph, B::Sends, B::Birth>,
70        schedules: InterpreterRequests<ScheduleAfter>,
71    ) -> Actions<
72        behavior::BehaviorAddr<B>,
73        B::Ph,
74        SendLayer<InterpreterRequests<ScheduleAfter>, B::Sends>,
75        B::Birth,
76    > {
77        actions.map_sends(|inner| SendLayer::new(schedules, inner))
78    }
79}
80
81impl<B: Behavior + behavior::BehaviorBase> behavior::BehaviorBase for OneShot<B> {
82    type Base = B::Base;
83
84    fn base(&self) -> &Self::Base {
85        self.inner.base()
86    }
87}
88
89impl<B, A, Ph, Sends, Br> Behavior for OneShot<B>
90where
91    A: Address,
92    Sends: SendEffects + behavior::SendsFor<B::Event>,
93    Br: BirthMode,
94    B: Behavior<Ph = Ph, Sends = Sends, Birth = Br>,
95    B::Protocol: behavior::Protocol<Addr = A>,
96{
97    type Protocol = B::Protocol;
98    type Event = TimedEvent<B::Event>;
99    type Sends = SendLayer<InterpreterRequests<ScheduleAfter>, Sends>;
100    type Ph = Ph;
101    type Error = B::Error;
102    type Birth = Br;
103
104    fn init(&mut self, _: behavior::InitializationTurn) -> BehaviorActed<Self> {
105        let actions = behavior::initialize(&mut self.inner)?;
106        let schedules = if matches!(actions.become_, Step::Stop(_)) {
107            self.lease.disarm();
108            InterpreterRequests::empty()
109        } else {
110            self.schedule()
111        };
112        Ok(Self::wrap(actions, schedules))
113    }
114
115    fn transition(&mut self, _: behavior::ActiveTurn, event: Self::Event) -> BehaviorActed<Self> {
116        match event {
117            EventLayer::Owned(elapsed) if elapsed.id == self.id => {
118                match self.lease.accept(elapsed.generation) {
119                    TimerAdmission::Accepted => {
120                        let actions = (self.on_elapsed)(&mut self.inner);
121                        Ok(Self::wrap(actions, InterpreterRequests::empty()))
122                    }
123                    TimerAdmission::Ignored => Ok(Actions::cont()),
124                }
125            }
126            EventLayer::Owned(_) => Ok(Actions::cont()),
127            EventLayer::Inner(event) => behavior::delegate_transition(&mut self.inner, event)
128                .map(|actions| Self::wrap(actions, InterpreterRequests::empty())),
129        }
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136    use crate::{Activate as _, TimerElapsed};
137    use behavior::{MailAddr, Never, NoBirths, Step, User};
138
139    struct Probe {
140        elapsed: usize,
141    }
142
143    impl behavior::BehaviorBase for Probe {
144        type Base = Self;
145
146        fn base(&self) -> &Self {
147            self
148        }
149    }
150
151    impl behavior::Protocol for Probe {
152        type Addr = MailAddr;
153        type Msg = ();
154    }
155
156    impl Behavior for Probe {
157        type Protocol = Self;
158        type Event = User<MailAddr, ()>;
159        type Sends = Vec<Never>;
160        type Ph = Never;
161        type Error = Never;
162        type Birth = NoBirths;
163
164        fn transition(&mut self, _: behavior::ActiveTurn, _: Self::Event) -> BehaviorActed<Self> {
165            Ok(Actions::cont())
166        }
167    }
168
169    fn mark(probe: &mut Probe) -> Actions<MailAddr, Never, Vec<Never>, NoBirths> {
170        probe.elapsed += 1;
171        Actions::cont()
172    }
173
174    #[test]
175    fn initialization_schedules_then_matching_generation_fires_once() {
176        let delay = Duration::from_millis(20);
177        let initialized = crate::OneShot::new(Probe { elapsed: 0 }, TimerId(7), delay, mark)
178            .initialize()
179            .unwrap();
180        assert!(initialized.actions.sends.inner.is_empty());
181        assert_eq!(
182            initialized.actions.sends.owned.as_slice(),
183            [ScheduleAfter::new(
184                TimerId(7),
185                crate::TimerGeneration(0),
186                delay
187            )]
188        );
189        assert!(initialized.actions.creates.is_empty());
190        assert!(matches!(initialized.actions.become_, Step::Continue));
191
192        let mut active = initialized.behavior;
193        let wrong_id = active
194            .on_path(TimerElapsed::new(TimerId(8), crate::TimerGeneration(0)))
195            .unwrap();
196        assert!(wrong_id.sends == SendLayer::empty());
197        assert_eq!(active.base().elapsed, 0);
198
199        let fired = active
200            .on_path(TimerElapsed::new(TimerId(7), crate::TimerGeneration(0)))
201            .unwrap();
202        assert!(fired.sends == SendLayer::empty());
203        assert_eq!(active.base().elapsed, 1);
204
205        let duplicate = active
206            .on_path(TimerElapsed::new(TimerId(7), crate::TimerGeneration(0)))
207            .unwrap();
208        assert_eq!(duplicate.sends, SendLayer::empty());
209        assert!(duplicate.creates.is_empty());
210        assert!(matches!(duplicate.become_, Step::Continue));
211        assert_eq!(active.base().elapsed, 1);
212    }
213}