Skip to main content

behavior/protocol/
mod.rs

1//! Neutral typed vocabulary for interpreter-originated event and service lanes.
2//!
3//! Concrete behavior transformations define the closed sum types that add
4//! these lanes. Keeping their values and construction capabilities here avoids
5//! dependencies between otherwise independent transformations.
6
7pub(crate) mod forward;
8
9use std::time::Duration;
10
11use tokio::time::Instant;
12
13use crate::behavior::Address;
14use crate::calculus::UserEvent;
15use crate::{Crash, CreationKind, Exit};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18pub struct TimerId(pub u64);
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
21pub struct TimerGeneration(pub u64);
22
23impl From<u64> for TimerId {
24    fn from(value: u64) -> Self {
25        Self(value)
26    }
27}
28
29impl From<TimerId> for u64 {
30    fn from(value: TimerId) -> Self {
31        value.0
32    }
33}
34
35impl From<u64> for TimerGeneration {
36    fn from(value: u64) -> Self {
37        Self(value)
38    }
39}
40
41impl From<TimerGeneration> for u64 {
42    fn from(value: TimerGeneration) -> Self {
43        value.0
44    }
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub struct ScheduleAt {
49    pub id: TimerId,
50    pub generation: TimerGeneration,
51    pub at: Instant,
52}
53
54impl ScheduleAt {
55    #[must_use]
56    pub const fn new(id: TimerId, generation: TimerGeneration, at: Instant) -> Self {
57        Self { id, generation, at }
58    }
59}
60
61impl From<(TimerId, TimerGeneration, Instant)> for ScheduleAt {
62    fn from((id, generation, at): (TimerId, TimerGeneration, Instant)) -> Self {
63        Self::new(id, generation, at)
64    }
65}
66
67/// Request scheduling relative to the interpreter's clock.
68///
69/// Constructing this value does not observe a clock. The interpreter resolves
70/// `after` only when it interprets the successful transition that emitted the
71/// request.
72#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct ScheduleAfter {
74    pub id: TimerId,
75    pub generation: TimerGeneration,
76    pub after: Duration,
77}
78
79impl ScheduleAfter {
80    #[must_use]
81    pub const fn new(id: TimerId, generation: TimerGeneration, after: Duration) -> Self {
82        Self {
83            id,
84            generation,
85            after,
86        }
87    }
88}
89
90impl From<(TimerId, TimerGeneration, Duration)> for ScheduleAfter {
91    fn from((id, generation, after): (TimerId, TimerGeneration, Duration)) -> Self {
92        Self::new(id, generation, after)
93    }
94}
95
96#[derive(Debug, Clone, Copy, PartialEq, Eq)]
97pub struct TimerElapsed {
98    pub id: TimerId,
99    pub generation: TimerGeneration,
100}
101
102impl TimerElapsed {
103    #[must_use]
104    pub const fn new(id: TimerId, generation: TimerGeneration) -> Self {
105        Self { id, generation }
106    }
107}
108
109impl From<(TimerId, TimerGeneration)> for TimerElapsed {
110    fn from((id, generation): (TimerId, TimerGeneration)) -> Self {
111        Self::new(id, generation)
112    }
113}
114
115pub trait TimeEvent: UserEvent {
116    fn time_reached(event: TimerElapsed) -> Option<Self>;
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq)]
120pub struct ObservePeer<A> {
121    pub peer: A,
122}
123
124impl<A> From<A> for ObservePeer<A> {
125    fn from(peer: A) -> Self {
126        Self { peer }
127    }
128}
129
130impl<A> ObservePeer<A> {
131    #[must_use]
132    pub const fn new(peer: A) -> Self {
133        Self { peer }
134    }
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub struct PeerStopped<A: Address> {
139    pub peer: A,
140    pub outcome: Result<Exit<A>, Crash>,
141}
142
143impl<A: Address> PeerStopped<A> {
144    #[must_use]
145    pub fn new(peer: A, outcome: Result<Exit<A>, Crash>) -> Self {
146        Self { peer, outcome }
147    }
148}
149
150pub trait PeerEvent: UserEvent {
151    fn peer_stopped(event: PeerStopped<Self::Addr>) -> Option<Self>;
152}
153
154#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct ChildStopped<A: Address> {
156    pub nonce: A::Nonce,
157    pub outcome: Result<Exit<A>, Crash>,
158    pub at: Instant,
159}
160
161impl<A: Address> ChildStopped<A> {
162    #[must_use]
163    pub fn new(nonce: A::Nonce, outcome: Result<Exit<A>, Crash>, at: Instant) -> Self {
164        Self { nonce, outcome, at }
165    }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub struct ObserveChild<N> {
170    pub nonce: N,
171}
172
173impl<N> ObserveChild<N> {
174    #[must_use]
175    pub const fn new(nonce: N) -> Self {
176        Self { nonce }
177    }
178}
179
180/// A proxy's request for its interpreter to report a worker termination to
181/// the proxy's parent. The interpreter supplies the emitting proxy's child
182/// nonce when constructing [`WorkerStopped`].
183#[derive(Debug, Clone, PartialEq, Eq)]
184pub struct ReportWorkerStopped<A: Address> {
185    pub worker: A::Nonce,
186    pub outcome: Result<Exit<A>, Crash>,
187    pub at: Instant,
188}
189
190impl<A: Address> ReportWorkerStopped<A> {
191    #[must_use]
192    pub fn new(worker: A::Nonce, outcome: Result<Exit<A>, Crash>, at: Instant) -> Self {
193        Self {
194            worker,
195            outcome,
196            at,
197        }
198    }
199}
200
201impl<A: Address> From<ChildStopped<A>> for ReportWorkerStopped<A> {
202    fn from(stopped: ChildStopped<A>) -> Self {
203        Self::new(stopped.nonce, stopped.outcome, stopped.at)
204    }
205}
206
207/// A worker termination reported by a still-live supervised proxy.
208#[derive(Debug, Clone, PartialEq, Eq)]
209pub struct WorkerStopped<A: Address> {
210    pub proxy: A::Nonce,
211    pub worker: A::Nonce,
212    pub outcome: Result<Exit<A>, Crash>,
213    pub at: Instant,
214}
215
216impl<A: Address> WorkerStopped<A> {
217    #[must_use]
218    pub fn new(
219        proxy: A::Nonce,
220        worker: A::Nonce,
221        outcome: Result<Exit<A>, Crash>,
222        at: Instant,
223    ) -> Self {
224        Self {
225            proxy,
226            worker,
227            outcome,
228            at,
229        }
230    }
231}
232
233impl<A: Address> From<(A::Nonce, ReportWorkerStopped<A>)> for WorkerStopped<A> {
234    fn from((proxy, stopped): (A::Nonce, ReportWorkerStopped<A>)) -> Self {
235        Self::new(proxy, stopped.worker, stopped.outcome, stopped.at)
236    }
237}
238
239pub trait ChildEvent: UserEvent {
240    fn child_stopped(event: ChildStopped<Self::Addr>) -> Option<Self>;
241}
242
243pub trait WorkerEvent: UserEvent {
244    fn worker_stopped(event: WorkerStopped<Self::Addr>) -> Option<Self>;
245}
246
247/// Why a staged fresh creation was not committed by an interpreter.
248///
249/// This is a closed semantic classification; interpreter-specific error
250/// values remain at the runtime boundary.
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub enum CreationRejection {
253    /// The creator-local nonce was already bound, so accepting the request
254    /// would overwrite rather than establish a fresh child.
255    NonceAlreadyBound,
256    /// The fresh child's initialization did not complete successfully.
257    InitializationFailed,
258    /// The interpreter could not allocate, install, or commit the fresh child.
259    EnvironmentFailed,
260}
261
262/// The committed result of one staged [`crate::Create`] request.
263///
264/// `Installed` is emitted only after fresh allocation, successful
265/// initialization, and binding at `nonce`. The replacement provenance is the
266/// provenance supplied by Behavior; an interpreter must never infer it from
267/// address reuse or creation order.
268#[derive(Debug, Clone, Copy, PartialEq, Eq)]
269pub struct CreationResolved<N> {
270    pub nonce: N,
271    pub kind: CreationKind<N>,
272    pub result: Result<(), CreationRejection>,
273}
274
275impl<N> CreationResolved<N> {
276    #[must_use]
277    pub const fn new(
278        nonce: N,
279        kind: CreationKind<N>,
280        result: Result<(), CreationRejection>,
281    ) -> Self {
282        Self {
283            nonce,
284            kind,
285            result,
286        }
287    }
288
289    #[must_use]
290    pub const fn installed(nonce: N, kind: CreationKind<N>) -> Self {
291        Self::new(nonce, kind, Ok(()))
292    }
293
294    /// A successfully committed ordinary birth.
295    #[must_use]
296    pub const fn birth(nonce: N) -> Self {
297        Self::installed(nonce, CreationKind::Birth)
298    }
299
300    /// A successfully committed replacement incarnation.
301    #[must_use]
302    pub const fn replacement_incarnation(nonce: N, replaces: N) -> Self {
303        Self::installed(nonce, CreationKind::ReplacementIncarnation { replaces })
304    }
305
306    #[must_use]
307    pub const fn rejected(nonce: N, kind: CreationKind<N>, rejection: CreationRejection) -> Self {
308        Self::new(nonce, kind, Err(rejection))
309    }
310}
311
312/// Ask the local interpreter to return the committed result of the same-action
313/// creation at `nonce` through the behavior's [`CreationEvent`] lane.
314#[derive(Debug, Clone, Copy, PartialEq, Eq)]
315pub struct ObserveCreation<N> {
316    pub nonce: N,
317}
318
319impl<N> ObserveCreation<N> {
320    #[must_use]
321    pub const fn new(nonce: N) -> Self {
322        Self { nonce }
323    }
324}
325
326pub trait CreationEvent: UserEvent {
327    fn creation_resolved(event: CreationResolved<<Self::Addr as Address>::Nonce>) -> Option<Self>;
328}
329
330/// Ask a proxy's interpreter to report a worker creation result to its parent.
331/// The interpreter supplies the emitting proxy's nonce.
332#[derive(Debug, Clone, Copy, PartialEq, Eq)]
333pub struct ReportWorkerCreationResolved<N> {
334    pub worker: N,
335    pub kind: CreationKind<N>,
336    pub result: Result<(), CreationRejection>,
337}
338
339impl<N> ReportWorkerCreationResolved<N> {
340    #[must_use]
341    pub const fn new(
342        worker: N,
343        kind: CreationKind<N>,
344        result: Result<(), CreationRejection>,
345    ) -> Self {
346        Self {
347            worker,
348            kind,
349            result,
350        }
351    }
352}
353
354impl<N> From<CreationResolved<N>> for ReportWorkerCreationResolved<N> {
355    fn from(resolved: CreationResolved<N>) -> Self {
356        Self::new(resolved.nonce, resolved.kind, resolved.result)
357    }
358}
359
360/// A worker creation result reported by a still-live supervised proxy.
361#[derive(Debug, Clone, Copy, PartialEq, Eq)]
362pub struct WorkerCreationResolved<N> {
363    pub proxy: N,
364    pub worker: N,
365    pub kind: CreationKind<N>,
366    pub result: Result<(), CreationRejection>,
367}
368
369impl<N> WorkerCreationResolved<N> {
370    #[must_use]
371    pub const fn new(
372        proxy: N,
373        worker: N,
374        kind: CreationKind<N>,
375        result: Result<(), CreationRejection>,
376    ) -> Self {
377        Self {
378            proxy,
379            worker,
380            kind,
381            result,
382        }
383    }
384}
385
386impl<N> From<(N, ReportWorkerCreationResolved<N>)> for WorkerCreationResolved<N> {
387    fn from((proxy, resolved): (N, ReportWorkerCreationResolved<N>)) -> Self {
388        Self::new(proxy, resolved.worker, resolved.kind, resolved.result)
389    }
390}
391
392pub trait WorkerCreationEvent: UserEvent {
393    fn worker_creation_resolved(
394        event: WorkerCreationResolved<<Self::Addr as Address>::Nonce>,
395    ) -> Option<Self>;
396}
397
398/// A request to finish through one serialized behavior transition.
399#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
400pub struct ShutdownRequested;
401
402pub trait ShutdownEvent: UserEvent {
403    fn shutdown_requested(event: ShutdownRequested) -> Option<Self>;
404}
405
406#[cfg(test)]
407mod tests {
408    use super::*;
409    use crate::MailAddr;
410
411    #[test]
412    fn lifecycle_conversions_preserve_every_semantic_field() {
413        let at = Instant::now();
414        let child = ChildStopped::<MailAddr>::new(3, Err(Crash::Failed), at);
415        let report = ReportWorkerStopped::from(child);
416        let worker = WorkerStopped::from((7, report));
417        assert_eq!(worker.proxy, 7);
418        assert_eq!(worker.worker, 3);
419        assert_eq!(worker.outcome, Err(Crash::Failed));
420        assert_eq!(worker.at, at);
421
422        let creation = CreationResolved::<u64>::rejected(
423            4,
424            CreationKind::replacement_of(3),
425            CreationRejection::EnvironmentFailed,
426        );
427        let report = ReportWorkerCreationResolved::from(creation);
428        let worker = WorkerCreationResolved::from((7, report));
429        assert_eq!(worker.proxy, 7);
430        assert_eq!(worker.worker, 4);
431        assert_eq!(worker.kind, CreationKind::replacement_of(3));
432        assert_eq!(worker.result, Err(CreationRejection::EnvironmentFailed));
433    }
434
435    #[test]
436    fn timer_newtypes_and_requests_have_lossless_construction() {
437        let id = TimerId::from(2);
438        let generation = TimerGeneration::from(5);
439        assert_eq!(u64::from(id), 2);
440        assert_eq!(u64::from(generation), 5);
441        assert_eq!(
442            TimerElapsed::from((id, generation)),
443            TimerElapsed::new(id, generation)
444        );
445    }
446}