actr_runtime/lifecycle/
actr_system.rs

1//! ActrSystem - Generic-free infrastructure
2
3use actr_config::Config;
4use actr_framework::Workload;
5use actr_protocol::ActorResult;
6use std::sync::Arc;
7
8use super::ActrNode;
9use crate::context_factory::ContextFactory;
10
11// Use types from sub-crates
12use crate::wire::webrtc::{
13    ReconnectConfig, SignalingClient, SignalingConfig, WebSocketSignalingClient,
14};
15use actr_mailbox::{DeadLetterQueue, Mailbox};
16
17/// ActrSystem - Runtime infrastructure (generic-free)
18///
19/// # Design Philosophy
20/// - Phase 1: Create pure runtime framework
21/// - Knows nothing about business logic types
22/// - Transforms into ActrNode<W> via attach()
23pub struct ActrSystem {
24    /// Runtime configuration
25    config: Config,
26
27    /// SQLite persistent mailbox
28    mailbox: Arc<dyn Mailbox>,
29
30    /// Dead Letter Queue for poison messages
31    dlq: Arc<dyn DeadLetterQueue>,
32
33    /// Context factory (with inproc_gate ready, outproc_gate deferred)
34    context_factory: ContextFactory,
35
36    /// Signaling client
37    signaling_client: Arc<dyn SignalingClient>,
38}
39
40impl ActrSystem {
41    /// Create new ActrSystem
42    ///
43    /// # Errors
44    /// - Mailbox initialization failed
45    /// - Transport initialization failed
46    pub async fn new(config: Config) -> ActorResult<Self> {
47        tracing::info!("🚀 Initializing ActrSystem");
48
49        // Initialize Mailbox (using SqliteMailbox implementation)
50        let mailbox_path = config
51            .mailbox_path
52            .as_ref()
53            .map(|p| p.to_string_lossy().to_string())
54            .unwrap_or_else(|| ":memory:".to_string());
55
56        tracing::info!("📂 Mailbox database path: {}", mailbox_path);
57
58        let mailbox: Arc<dyn Mailbox> = Arc::new(
59            actr_mailbox::SqliteMailbox::new(&mailbox_path)
60                .await
61                .map_err(|e| {
62                    actr_protocol::ProtocolError::TransportError(format!(
63                        "Mailbox init failed: {e}"
64                    ))
65                })?,
66        );
67
68        // Initialize Dead Letter Queue
69        // Use same path as mailbox, DLQ will create separate table in same database
70        let dlq_path = if mailbox_path == ":memory:" {
71            ":memory:".to_string() // Separate in-memory DB for DLQ
72        } else {
73            format!("{mailbox_path}.dlq") // Separate file for DLQ
74        };
75
76        let dlq: Arc<dyn DeadLetterQueue> = Arc::new(
77            actr_mailbox::SqliteDeadLetterQueue::new_standalone(&dlq_path)
78                .await
79                .map_err(|e| {
80                    actr_protocol::ProtocolError::TransportError(format!("DLQ init failed: {e}"))
81                })?,
82        );
83        tracing::info!("✅ Dead Letter Queue initialized");
84
85        // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
86        // Initialize inproc infrastructure (Shell/Local communication - immediately available)
87        // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
88        use crate::outbound::{InprocOutGate, OutGate};
89        use crate::transport::InprocTransportManager;
90
91        // Create TWO separate InprocTransportManager instances for bidirectional communication
92        // This ensures Shell's pending_requests and Workload's pending_requests are separate
93
94        // Direction 1: Shell → Workload (REQUEST)
95        let shell_to_workload = Arc::new(InprocTransportManager::new());
96        tracing::debug!("✨ Created shell_to_workload InprocTransportManager");
97
98        // Direction 2: Workload → Shell (RESPONSE)
99        let workload_to_shell = Arc::new(InprocTransportManager::new());
100        tracing::debug!("✨ Created workload_to_shell InprocTransportManager");
101
102        // Shell uses shell_to_workload for sending
103        let inproc_gate =
104            OutGate::InprocOut(Arc::new(InprocOutGate::new(shell_to_workload.clone())));
105
106        // Create DataStreamRegistry for DataStream callbacks
107        let data_stream_registry = Arc::new(crate::inbound::DataStreamRegistry::new());
108        tracing::debug!("✨ Created DataStreamRegistry");
109
110        // Create MediaFrameRegistry for MediaTrack callbacks
111        let media_frame_registry = Arc::new(crate::inbound::MediaFrameRegistry::new());
112        tracing::debug!("✨ Created MediaFrameRegistry");
113
114        // ContextFactory holds both managers and registries
115        let context_factory = ContextFactory::new(
116            inproc_gate,
117            shell_to_workload.clone(),
118            workload_to_shell.clone(),
119            data_stream_registry,
120            media_frame_registry,
121        );
122
123        tracing::info!("✅ Inproc infrastructure initialized (bidirectional Shell ↔ Workload)");
124
125        // Initialize signaling client (using WebSocketSignalingClient implementation)
126        let signaling_config = SignalingConfig {
127            server_url: config.signaling_url.clone(),
128            connection_timeout: 30,
129            heartbeat_interval: 30,
130            reconnect_config: ReconnectConfig::default(),
131            auth_config: None,
132        };
133        let signaling_client: Arc<dyn SignalingClient> =
134            Arc::new(WebSocketSignalingClient::new(signaling_config));
135
136        tracing::info!("✅ ActrSystem initialized");
137
138        Ok(Self {
139            config,
140            mailbox,
141            dlq,
142            context_factory,
143            signaling_client,
144        })
145    }
146
147    /// Attach Workload, transform into ActrNode<W>
148    ///
149    /// # Type Inference
150    /// - Infer W::Dispatcher from W
151    /// - Compiler monomorphizes ActrNode<W>
152    /// - Completely zero-dyn, full inline chain
153    ///
154    /// # Consumes self
155    /// - Move ensures can only be called once
156    /// - Embodies one-actor-per-instance principle
157    pub fn attach<W: Workload>(self, workload: W) -> ActrNode<W> {
158        tracing::info!("📦 Attaching workload");
159
160        ActrNode {
161            config: self.config,
162            workload: Arc::new(workload),
163            mailbox: self.mailbox,
164            dlq: self.dlq,
165            context_factory: Some(self.context_factory), // Initialized with inproc_gate ready
166            signaling_client: self.signaling_client,
167            actor_id: None,              // Obtained after startup
168            credential: None,            // Obtained after startup
169            webrtc_coordinator: None,    // Created after startup
170            webrtc_gate: None,           // Created after startup
171            inproc_mgr: None,            // Set after startup
172            workload_to_shell_mgr: None, // Set after startup
173            shutdown_token: tokio_util::sync::CancellationToken::new(),
174        }
175    }
176}