actr_framework/
dest.rs

1//! Destination identifier for Actor communication
2//!
3//! Defines the `Dest` enum with three-way destination distinction:
4//! - **Shell**: Workload → App (inproc reverse channel)
5//! - **Local**: Target local Workload (inproc from App, outproc short-circuit from Workload)
6//! - **Actor**: Remote Actor (full outproc)
7//!
8//! # Design Rationale
9//!
10//! The three-way distinction provides:
11//! - **Clear semantics**: Shell/Local/Actor have distinct meanings
12//! - **Symmetric communication**: App ↔ Workload bidirectional calls
13//! - **Protocol consistency**: Workload self-calls use full serialization (same as remote)
14//! - **Transparent optimization**: Transport layer can short-circuit Local calls
15//!
16//! # Usage
17//!
18//! **Shell 侧 (App)**:
19//! ```rust,ignore
20//! // Call local Workload (隐含 Dest::Local)
21//! running_node.call(request).await?;  // No target parameter
22//! ```
23//!
24//! **Actr 侧 (Workload)**:
25//! ```rust,ignore
26//! // Call App
27//! ctx.call(&Dest::Shell, request).await?;
28//!
29//! // Call self (outproc short-circuit)
30//! ctx.call(&Dest::Local, request).await?;
31//!
32//! // Call remote Actor
33//! ctx.call(&Dest::Actor(server_id), request).await?;
34//! ```
35//!
36//! # Placement in actr-framework
37//!
38//! `Dest` is placed in `actr-framework` (not `actr-protocol`) because:
39//! - It's an API-level abstraction, not a protocol data type
40//! - It's used directly by the `Context` trait
41//! - `RpcEnvelope` (in protocol layer) does not contain destination information
42//! - The runtime layer implements the routing logic based on `Dest`
43
44use actr_protocol::ActrId;
45use std::hash::{Hash, Hasher};
46
47/// Destination identifier
48///
49/// Three-way destination for message routing.
50///
51/// # Semantics
52///
53/// - **`Dest::Shell`**: Workload → App (inproc 反向通道)
54///   - Used by Workload to call App side
55///   - Routed through `InprocOutGate` (zero serialization)
56///   - Example: Workload pushing notifications to App
57///
58/// - **`Dest::Local`**: Target local Workload
59///   - From App: routed through `InprocOutGate` (zero serialization)
60///   - From Workload: routed through `OutprocOutGate` (full serialization, short-circuit at transport)
61///   - Example: App calling its local Workload, or Workload calling itself
62///
63/// - **`Dest::Actor(ActrId)`**: Remote Actor (full outproc)
64///   - Used for cross-process Actor communication
65///   - Routed through `OutprocOutGate` (WebRTC/WebSocket)
66///   - Example: ClientWorkload calling RemoteServer
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum Dest {
69    /// Local Shell - Workload 调用 App 侧 (inproc 反向通道)
70    Shell,
71
72    /// Local Workload - 调用本地 Workload (从 App: inproc, 从 Workload: outproc 短接)
73    Local,
74
75    /// Remote Actor - 跨进程通信 (WebRTC/WebSocket)
76    Actor(ActrId),
77}
78
79impl Hash for Dest {
80    #[inline]
81    fn hash<H: Hasher>(&self, state: &mut H) {
82        match self {
83            Dest::Shell => {
84                0u8.hash(state);
85            }
86            Dest::Local => {
87                1u8.hash(state);
88            }
89            Dest::Actor(id) => {
90                2u8.hash(state);
91                id.hash(state);
92            }
93        }
94    }
95}
96
97impl Dest {
98    /// Create Shell destination
99    #[inline]
100    pub fn shell() -> Self {
101        Dest::Shell
102    }
103
104    /// Create Local destination
105    #[inline]
106    pub fn local() -> Self {
107        Dest::Local
108    }
109
110    /// Create Actor destination
111    #[inline]
112    pub fn actor(id: ActrId) -> Self {
113        Dest::Actor(id)
114    }
115
116    /// Check if this is a Shell destination
117    #[inline]
118    pub fn is_shell(&self) -> bool {
119        matches!(self, Dest::Shell)
120    }
121
122    /// Check if this is a Local destination
123    #[inline]
124    pub fn is_local(&self) -> bool {
125        matches!(self, Dest::Local)
126    }
127
128    /// Check if this is an Actor destination
129    #[inline]
130    pub fn is_actor(&self) -> bool {
131        matches!(self, Dest::Actor(_))
132    }
133
134    /// Get ActrId (if this is an Actor destination)
135    ///
136    /// Returns `None` for `Dest::Shell` or `Dest::Local`.
137    #[inline]
138    pub fn as_actor_id(&self) -> Option<&ActrId> {
139        match self {
140            Dest::Actor(id) => Some(id),
141            _ => None,
142        }
143    }
144}
145
146impl From<ActrId> for Dest {
147    #[inline]
148    fn from(id: ActrId) -> Self {
149        Dest::Actor(id)
150    }
151}
152
153#[cfg(test)]
154mod tests {
155    use super::*;
156
157    #[test]
158    fn test_dest_creation() {
159        let shell_dest = Dest::shell();
160        assert!(shell_dest.is_shell());
161        assert!(!shell_dest.is_local());
162        assert!(!shell_dest.is_actor());
163
164        let local_dest = Dest::local();
165        assert!(!local_dest.is_shell());
166        assert!(local_dest.is_local());
167        assert!(!local_dest.is_actor());
168
169        let id = ActrId::default();
170        let actor_dest = Dest::actor(id);
171        assert!(!actor_dest.is_shell());
172        assert!(!actor_dest.is_local());
173        assert!(actor_dest.is_actor());
174    }
175
176    #[test]
177    fn test_dest_hash() {
178        use std::collections::HashMap;
179
180        let id1 = ActrId::default();
181        let mut id2 = ActrId::default();
182        id2.serial_number = 1; // Ensure different ID
183
184        let mut map = HashMap::new();
185        map.insert(Dest::shell(), "shell");
186        map.insert(Dest::local(), "local");
187        map.insert(Dest::actor(id1), "actor1");
188        map.insert(Dest::actor(id2), "actor2");
189
190        assert_eq!(map.len(), 4);
191    }
192
193    #[test]
194    fn test_dest_as_actor_id() {
195        let shell_dest = Dest::shell();
196        assert_eq!(shell_dest.as_actor_id(), None);
197
198        let local_dest = Dest::local();
199        assert_eq!(local_dest.as_actor_id(), None);
200
201        let id = ActrId::default();
202        let actor_dest = Dest::actor(id.clone());
203        assert_eq!(actor_dest.as_actor_id(), Some(&id));
204    }
205}