Skip to main content

atomr_core/actor/
traits.rs

1//! Core `Actor` trait and message envelope.
2
3use async_trait::async_trait;
4
5use super::context::Context;
6use super::metadata::Metadata;
7use super::sender::Sender;
8use crate::supervision::{Directive, SupervisorStrategy};
9
10/// Envelope that carries a user message plus a typed [`Sender`].
11///
12/// `M` is the actor's user message type. The [`Sender`] preserves the
13/// origin's identity end-to-end (no `Any::downcast` on reply paths) —
14/// see `docs/idiomatic-rust.md` (P-1) and Phase 1 of
15/// `docs/full-port-plan.md`.
16pub struct MessageEnvelope<M> {
17    pub message: M,
18    pub sender: Sender,
19    /// Trace context + baggage propagated across hops (FR-10). Empty unless a
20    /// sender attached it via [`tell_with_meta`](super::ActorRef::tell_with_meta)
21    /// or an interceptor injected it.
22    pub metadata: Metadata,
23}
24
25impl<M> MessageEnvelope<M> {
26    pub fn new(message: M) -> Self {
27        Self { message, sender: Sender::None, metadata: Metadata::new() }
28    }
29
30    /// Construct with a typed [`Sender`].
31    pub fn with_typed_sender(message: M, sender: Sender) -> Self {
32        Self { message, sender, metadata: Metadata::new() }
33    }
34
35    /// Construct with a typed [`Sender`] and [`Metadata`].
36    pub fn with_meta(message: M, sender: Sender, metadata: Metadata) -> Self {
37        Self { message, sender, metadata }
38    }
39}
40
41/// The user-facing `Actor` trait.
42///
43/// is expressed here as: each actor has an
44/// associated `Msg` type (typically an enum) and implements an async
45/// `handle` that matches on it.
46#[async_trait]
47pub trait Actor: Sized + Send + 'static {
48    type Msg: Send + 'static;
49
50    /// Process a single message.
51    async fn handle(&mut self, ctx: &mut Context<Self>, msg: Self::Msg);
52
53    /// Called once before the first message.
54    async fn pre_start(&mut self, _ctx: &mut Context<Self>) {}
55
56    /// Called after the actor has been stopped.
57    async fn post_stop(&mut self, _ctx: &mut Context<Self>) {}
58
59    /// Called when the actor is about to be restarted by the supervisor.
60    async fn pre_restart(&mut self, _ctx: &mut Context<Self>, _err: &str) {}
61
62    /// Called after a restart.
63    async fn post_restart(&mut self, _ctx: &mut Context<Self>, _err: &str) {}
64
65    /// Called when a watched actor terminates. The `path` argument is
66    /// the path of the actor that just stopped. Default is a no-op.
67    /// Implementations may translate this into a user-visible message
68    /// (the Python binding does this for `Terminated` events).
69    async fn on_terminated(&mut self, _ctx: &mut Context<Self>, _path: &super::path::ActorPath) {}
70
71    /// Called when the supervisor pushes a *graded* operating-mode change
72    /// ([`Directive::Throttle`], [`Directive::Suspend`], or
73    /// [`Directive::ResumeFrom`]) into this still-running actor — no restart,
74    /// state preserved (FR-6). The actor is expected to gate its own outbound
75    /// effects (order rate/size, flat-only, etc.) accordingly. Default no-op.
76    ///
77    /// The crash-recovery directives (`Resume`/`Restart`/`Stop`/`Escalate`) do
78    /// **not** invoke this hook — they keep their existing lifecycle semantics.
79    async fn on_directive(&mut self, _ctx: &mut Context<Self>, _directive: &Directive) {}
80
81    /// The supervisor strategy this actor applies to its own children.
82    fn supervisor_strategy(&self) -> SupervisorStrategy {
83        SupervisorStrategy::default()
84    }
85}