ringkernel_procint/actors/
runtime.rs

1//! GPU actor runtime for process intelligence.
2//!
3//! Manages GPU resources and kernel execution.
4
5use std::time::{Duration, Instant};
6
7/// GPU actor runtime configuration.
8#[derive(Debug, Clone)]
9pub struct RuntimeConfig {
10    /// Target frames per second for processing.
11    pub target_fps: u32,
12    /// Maximum batch size.
13    pub max_batch_size: usize,
14    /// Enable GPU acceleration.
15    pub use_gpu: bool,
16    /// GPU device ID (0 = default).
17    pub device_id: u32,
18}
19
20impl Default for RuntimeConfig {
21    fn default() -> Self {
22        Self {
23            target_fps: 60,
24            max_batch_size: 4096,
25            use_gpu: true,
26            device_id: 0,
27        }
28    }
29}
30
31/// GPU actor runtime.
32#[derive(Debug)]
33pub struct GpuActorRuntime {
34    /// Configuration.
35    config: RuntimeConfig,
36    /// Is initialized.
37    initialized: bool,
38    /// GPU device info.
39    device_info: Option<DeviceInfo>,
40    /// Frame timing.
41    frame_timer: FrameTimer,
42}
43
44impl Default for GpuActorRuntime {
45    fn default() -> Self {
46        Self::new(RuntimeConfig::default())
47    }
48}
49
50impl GpuActorRuntime {
51    /// Create a new runtime.
52    pub fn new(config: RuntimeConfig) -> Self {
53        Self {
54            config,
55            initialized: false,
56            device_info: None,
57            frame_timer: FrameTimer::new(60),
58        }
59    }
60
61    /// Initialize the runtime.
62    pub fn init(&mut self) -> Result<(), String> {
63        if self.initialized {
64            return Ok(());
65        }
66
67        // Check for GPU availability
68        if self.config.use_gpu {
69            self.device_info = Self::detect_gpu();
70        }
71
72        self.frame_timer = FrameTimer::new(self.config.target_fps);
73        self.initialized = true;
74
75        Ok(())
76    }
77
78    /// Detect available GPU.
79    fn detect_gpu() -> Option<DeviceInfo> {
80        // Check for CUDA (simplified check)
81        #[cfg(feature = "cuda")]
82        {
83            Some(DeviceInfo {
84                name: "CUDA Device".to_string(),
85                backend: GpuBackend::Cuda,
86                memory_mb: 8192,
87                compute_capability: "8.6".to_string(),
88            })
89        }
90
91        #[cfg(not(feature = "cuda"))]
92        {
93            None
94        }
95    }
96
97    /// Check if GPU is available.
98    pub fn has_gpu(&self) -> bool {
99        self.device_info.is_some()
100    }
101
102    /// Get device info.
103    pub fn device_info(&self) -> Option<&DeviceInfo> {
104        self.device_info.as_ref()
105    }
106
107    /// Get backend name.
108    pub fn backend_name(&self) -> &'static str {
109        match &self.device_info {
110            Some(info) => info.backend.name(),
111            None => "CPU",
112        }
113    }
114
115    /// Start frame timing.
116    pub fn begin_frame(&mut self) {
117        self.frame_timer.begin_frame();
118    }
119
120    /// End frame and get timing info.
121    pub fn end_frame(&mut self) -> FrameTiming {
122        self.frame_timer.end_frame()
123    }
124
125    /// Get target frame duration.
126    pub fn target_frame_duration(&self) -> Duration {
127        Duration::from_secs_f64(1.0 / self.config.target_fps as f64)
128    }
129
130    /// Should skip frame (for throttling).
131    pub fn should_skip_frame(&self) -> bool {
132        self.frame_timer.should_skip()
133    }
134
135    /// Get current FPS.
136    pub fn current_fps(&self) -> f32 {
137        self.frame_timer.current_fps()
138    }
139}
140
141/// GPU device information.
142#[derive(Debug, Clone)]
143pub struct DeviceInfo {
144    /// Device name.
145    pub name: String,
146    /// GPU backend.
147    pub backend: GpuBackend,
148    /// Available memory in MB.
149    pub memory_mb: u32,
150    /// Compute capability (CUDA) or version.
151    pub compute_capability: String,
152}
153
154/// GPU backend type.
155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
156pub enum GpuBackend {
157    /// NVIDIA CUDA backend.
158    Cuda,
159    /// WebGPU cross-platform backend.
160    Wgpu,
161    /// Apple Metal backend.
162    Metal,
163    /// CPU fallback.
164    Cpu,
165}
166
167impl GpuBackend {
168    /// Get backend name.
169    pub fn name(&self) -> &'static str {
170        match self {
171            GpuBackend::Cuda => "CUDA",
172            GpuBackend::Wgpu => "WebGPU",
173            GpuBackend::Metal => "Metal",
174            GpuBackend::Cpu => "CPU",
175        }
176    }
177}
178
179/// Frame timing helper.
180#[derive(Debug)]
181pub struct FrameTimer {
182    /// Target FPS.
183    target_fps: u32,
184    /// Last frame start.
185    frame_start: Instant,
186    /// Last frame duration.
187    last_frame_duration: Duration,
188    /// Smoothed FPS.
189    smoothed_fps: f32,
190    /// Frame count.
191    frame_count: u64,
192    /// Accumulated time for FPS calculation.
193    accumulated_time: Duration,
194}
195
196impl FrameTimer {
197    /// Create a new frame timer.
198    pub fn new(target_fps: u32) -> Self {
199        Self {
200            target_fps,
201            frame_start: Instant::now(),
202            last_frame_duration: Duration::ZERO,
203            smoothed_fps: target_fps as f32,
204            frame_count: 0,
205            accumulated_time: Duration::ZERO,
206        }
207    }
208
209    /// Begin frame timing.
210    pub fn begin_frame(&mut self) {
211        self.frame_start = Instant::now();
212    }
213
214    /// End frame timing.
215    pub fn end_frame(&mut self) -> FrameTiming {
216        self.last_frame_duration = self.frame_start.elapsed();
217        self.accumulated_time += self.last_frame_duration;
218        self.frame_count += 1;
219
220        // Update smoothed FPS
221        if self.accumulated_time >= Duration::from_secs(1) {
222            self.smoothed_fps = self.frame_count as f32 / self.accumulated_time.as_secs_f32();
223            self.accumulated_time = Duration::ZERO;
224            self.frame_count = 0;
225        }
226
227        FrameTiming {
228            duration: self.last_frame_duration,
229            fps: self.smoothed_fps,
230            frame_budget_used: self.last_frame_duration.as_secs_f32()
231                / (1.0 / self.target_fps as f32),
232        }
233    }
234
235    /// Check if should skip frame (over budget).
236    pub fn should_skip(&self) -> bool {
237        let target_duration = Duration::from_secs_f64(1.0 / self.target_fps as f64);
238        self.last_frame_duration > target_duration * 2
239    }
240
241    /// Get current FPS.
242    pub fn current_fps(&self) -> f32 {
243        self.smoothed_fps
244    }
245}
246
247/// Frame timing information.
248#[derive(Debug, Clone, Copy)]
249pub struct FrameTiming {
250    /// Frame duration.
251    pub duration: Duration,
252    /// Current FPS.
253    pub fps: f32,
254    /// Percentage of frame budget used (1.0 = exactly on target).
255    pub frame_budget_used: f32,
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    #[test]
263    fn test_runtime_creation() {
264        let runtime = GpuActorRuntime::new(RuntimeConfig::default());
265        assert!(!runtime.initialized);
266    }
267
268    #[test]
269    fn test_runtime_init() {
270        let mut runtime = GpuActorRuntime::new(RuntimeConfig::default());
271        let result = runtime.init();
272        assert!(result.is_ok());
273        assert!(runtime.initialized);
274    }
275
276    #[test]
277    fn test_frame_timer() {
278        let mut timer = FrameTimer::new(60);
279        timer.begin_frame();
280        std::thread::sleep(Duration::from_millis(10));
281        let timing = timer.end_frame();
282        assert!(timing.duration >= Duration::from_millis(10));
283    }
284}