Skip to main content

actr_framework/
dest.rs

1//! Destination identifier for Actor communication
2//!
3//! Defines the `Dest` enum with three-way destination distinction:
4//! - **Host**: this node's host side (the process embedding the workload)
5//! - **Workload**: this node's workload side (self when sent from the workload)
6//! - **Peer**: a remote node, addressed by `ActrId`
7//!
8//! # Design Rationale
9//!
10//! All three variants answer the same question — *which role receives the
11//! message* — relative to the sending node:
12//! - **Consistent axis**: Host / Workload / Peer are role nouns; `Peer` makes
13//!   "crosses the network" visible in the name
14//! - **Symmetric communication**: Host ↔ Workload bidirectional calls
15//! - **Protocol consistency**: Workload self-calls use full serialization (same as remote)
16//! - **Transparent optimization**: Transport layer can short-circuit Workload calls
17//!
18//! # Usage
19//!
20//! **Host side (embedding process)**:
21//! ```rust,ignore
22//! // Call local Workload (implies Dest::Workload)
23//! running_node.call(request).await?;  // No target parameter
24//! ```
25//!
26//! **Workload side**:
27//! ```rust,ignore
28//! // Call the host side
29//! ctx.call(&Dest::Host, request).await?;
30//!
31//! // Call self (outproc short-circuit)
32//! ctx.call(&Dest::Workload, request).await?;
33//!
34//! // Call a remote peer
35//! ctx.call(&Dest::Peer(server_id), request).await?;
36//! ```
37//!
38//! # Placement in actr-framework
39//!
40//! `Dest` is placed in `actr-framework` (not `actr-protocol`) because:
41//! - It's an API-level abstraction, not a protocol data type
42//! - It's used directly by the `Context` trait
43//! - `RpcEnvelope` (in protocol layer) does not contain destination information
44//! - The runtime layer implements the routing logic based on `Dest`
45
46use actr_protocol::ActrId;
47
48/// Destination identifier
49///
50/// Three-way destination for message routing.
51///
52/// # Semantics
53///
54/// - **`Dest::Host`**: Workload -> host side (inproc reverse channel)
55///   - Used by the Workload to call the process embedding it
56///   - Routed through `HostGate` (zero serialization)
57///   - Example: Workload pushing notifications to the host application
58///
59/// - **`Dest::Workload`**: Target this node's Workload
60///   - From the host side: routed through `HostGate` (zero serialization)
61///   - From the Workload: routed through `PeerGate` (full serialization, short-circuit at transport)
62///   - Example: host calling its local Workload, or Workload calling itself
63///
64/// - **`Dest::Peer(ActrId)`**: Remote node (full outproc)
65///   - Used for cross-process Actor communication
66///   - Routed through `PeerGate` (WebRTC/WebSocket)
67///   - Example: ClientWorkload calling RemoteServer
68#[derive(Debug, Clone, PartialEq, Eq, Hash)]
69pub enum Dest {
70    /// This node's host side - Workload calls the embedding process (inproc reverse channel)
71    Host,
72
73    /// This node's Workload - self when sent from the workload (from host: inproc, from Workload: outproc short-circuit)
74    Workload,
75
76    /// Remote node - cross-process communication (WebRTC/WebSocket)
77    Peer(ActrId),
78}
79
80impl Dest {
81    /// Create a Host destination
82    #[inline]
83    pub fn host() -> Self {
84        Dest::Host
85    }
86
87    /// Create a Workload destination
88    #[inline]
89    pub fn workload() -> Self {
90        Dest::Workload
91    }
92
93    /// Create a Peer destination
94    #[inline]
95    pub fn peer(id: ActrId) -> Self {
96        Dest::Peer(id)
97    }
98
99    /// Check if this is a Host destination
100    #[inline]
101    pub fn is_host(&self) -> bool {
102        matches!(self, Dest::Host)
103    }
104
105    /// Check if this is a Workload destination
106    #[inline]
107    pub fn is_workload(&self) -> bool {
108        matches!(self, Dest::Workload)
109    }
110
111    /// Check if this is a Peer destination
112    #[inline]
113    pub fn is_peer(&self) -> bool {
114        matches!(self, Dest::Peer(_))
115    }
116
117    /// Get the peer's ActrId if this is a Peer destination
118    #[inline]
119    pub fn as_peer_id(&self) -> Option<&ActrId> {
120        match self {
121            Dest::Peer(id) => Some(id),
122            _ => None,
123        }
124    }
125}
126
127impl From<ActrId> for Dest {
128    #[inline]
129    fn from(id: ActrId) -> Self {
130        Dest::Peer(id)
131    }
132}
133
134#[cfg(test)]
135mod tests {
136    use super::*;
137
138    #[test]
139    fn test_dest_creation() {
140        let host_dest = Dest::host();
141        assert!(host_dest.is_host());
142        assert!(!host_dest.is_workload());
143        assert!(!host_dest.is_peer());
144
145        let workload_dest = Dest::workload();
146        assert!(!workload_dest.is_host());
147        assert!(workload_dest.is_workload());
148        assert!(!workload_dest.is_peer());
149
150        let id = ActrId::default();
151        let peer_dest = Dest::peer(id);
152        assert!(!peer_dest.is_host());
153        assert!(!peer_dest.is_workload());
154        assert!(peer_dest.is_peer());
155    }
156
157    #[test]
158    fn test_dest_hash() {
159        use std::collections::HashMap;
160
161        let id1 = ActrId::default();
162        let id2 = ActrId {
163            serial_number: 1,
164            ..Default::default()
165        };
166
167        let mut map = HashMap::new();
168        map.insert(Dest::host(), "host");
169        map.insert(Dest::workload(), "workload");
170        map.insert(Dest::peer(id1), "peer1");
171        map.insert(Dest::peer(id2), "peer2");
172
173        assert_eq!(map.len(), 4);
174    }
175
176    #[test]
177    fn test_dest_as_peer_id() {
178        let host_dest = Dest::host();
179        assert_eq!(host_dest.as_peer_id(), None);
180
181        let workload_dest = Dest::workload();
182        assert_eq!(workload_dest.as_peer_id(), None);
183
184        let id = ActrId {
185            serial_number: 7,
186            ..Default::default()
187        };
188        let peer_dest = Dest::peer(id.clone());
189        assert_eq!(peer_dest.as_peer_id(), Some(&id));
190    }
191
192    #[test]
193    fn test_dest_from_actr_id() {
194        let id = ActrId::default();
195        let dest: Dest = id.clone().into();
196        assert_eq!(dest, Dest::Peer(id));
197    }
198}