1use super::canvas::GridCanvas;
4use super::controls;
5use crate::simulation::{
6 AcousticParams, CellType, EducationalProcessor, KernelGrid, SimulationGrid, SimulationMode,
7};
8
9#[cfg(feature = "cuda")]
10use crate::simulation::CudaPackedBackend;
11
12use iced::widget::{container, row, Canvas};
13use iced::{Element, Length, Size, Subscription, Task, Theme};
14use ringkernel::prelude::Backend;
15use std::sync::Arc;
16use std::time::{Duration, Instant};
17use tokio::sync::Mutex;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum ComputeBackend {
22 Cpu,
24 GpuActor,
26 #[cfg(feature = "cuda")]
28 CudaPacked,
29}
30
31impl std::fmt::Display for ComputeBackend {
32 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33 match self {
34 ComputeBackend::Cpu => write!(f, "CPU (SIMD+Rayon)"),
35 ComputeBackend::GpuActor => write!(f, "GPU Actor"),
36 #[cfg(feature = "cuda")]
37 ComputeBackend::CudaPacked => write!(f, "CUDA Packed"),
38 }
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
44pub enum DrawMode {
45 #[default]
47 Impulse,
48 Absorber,
50 Reflector,
52 Erase,
54}
55
56impl std::fmt::Display for DrawMode {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 match self {
59 DrawMode::Impulse => write!(f, "Impulse"),
60 DrawMode::Absorber => write!(f, "Absorber"),
61 DrawMode::Reflector => write!(f, "Reflector"),
62 DrawMode::Erase => write!(f, "Erase"),
63 }
64 }
65}
66
67enum SimulationEngine {
69 Cpu(SimulationGrid),
71 Gpu(Arc<Mutex<KernelGrid>>),
73 #[cfg(feature = "cuda")]
75 CudaPacked(Arc<std::sync::Mutex<CudaPackedBackend>>),
76 Switching,
78}
79
80pub struct WaveSimApp {
82 engine: SimulationEngine,
84 canvas: GridCanvas,
86
87 grid_width: u32,
89 grid_height: u32,
90
91 grid_width_input: String,
94 grid_height_input: String,
96 speed_of_sound: f32,
98 cell_size: f32,
100 compute_backend: ComputeBackend,
102 legacy_backend: Backend,
104 is_running: bool,
106 show_stats: bool,
108 impulse_amplitude: f32,
110 draw_mode: DrawMode,
112 cell_types: Vec<Vec<CellType>>,
114 simulation_mode: SimulationMode,
116 educational_processor: EducationalProcessor,
118
119 last_frame: Instant,
122 fps: f32,
124 steps_per_sec: f32,
126 throughput: f64,
128 steps_last_frame: u32,
130 step_accumulator: u32,
132 time_accumulator: Duration,
134 last_stats_update: Instant,
136
137 cell_count: usize,
139 courant_number: f32,
140 max_pressure: f32,
141 total_energy: f32,
142}
143
144#[derive(Debug, Clone)]
146pub enum Message {
147 ToggleRunning,
149 Step,
151 Reset,
153
154 SpeedChanged(f32),
156 GridWidthChanged(String),
158 GridHeightChanged(String),
160 ApplyGridSize,
162
163 ImpulseAmplitudeChanged(f32),
165
166 CellSizeChanged(f32),
168
169 ComputeBackendChanged(ComputeBackend),
171 BackendSwitched(Result<Arc<Mutex<KernelGrid>>, String>),
173 #[cfg(feature = "cuda")]
175 CudaPackedSwitched(Result<Arc<std::sync::Mutex<CudaPackedBackend>>, String>),
176
177 CanvasClick(f32, f32),
179 CanvasRightClick(f32, f32),
181 DrawModeChanged(DrawMode),
183 ClearCellTypes,
185
186 Tick,
188 GpuStepCompleted(Vec<Vec<f32>>, f32, f32),
190 #[cfg(feature = "cuda")]
192 CudaPackedStepCompleted(Vec<f32>, u32),
193
194 ToggleStats,
196 SimulationModeChanged(SimulationMode),
198}
199
200impl WaveSimApp {
201 pub fn new() -> (Self, Task<Message>) {
203 let params = AcousticParams::new(343.0, 1.0);
204 let grid = SimulationGrid::new(64, 64, params.clone());
205 let pressure_grid = grid.get_pressure_grid();
206
207 let cell_count = grid.cell_count();
208 let courant_number = grid.params.courant_number();
209 let now = Instant::now();
210
211 (
212 Self {
213 engine: SimulationEngine::Cpu(grid),
214 canvas: GridCanvas::new(pressure_grid),
215 grid_width: 64,
216 grid_height: 64,
217 grid_width_input: "64".to_string(),
218 grid_height_input: "64".to_string(),
219 speed_of_sound: 343.0,
220 cell_size: 1.0,
221 compute_backend: ComputeBackend::Cpu,
222 legacy_backend: Backend::Cpu,
223 is_running: false,
224 show_stats: true,
225 impulse_amplitude: 1.0,
226 draw_mode: DrawMode::Impulse,
227 cell_types: vec![vec![CellType::Normal; 64]; 64],
228 simulation_mode: SimulationMode::Standard,
229 educational_processor: EducationalProcessor::default(),
230 last_frame: now,
231 fps: 0.0,
232 steps_per_sec: 0.0,
233 throughput: 0.0,
234 steps_last_frame: 0,
235 step_accumulator: 0,
236 time_accumulator: Duration::ZERO,
237 last_stats_update: now,
238 cell_count,
239 courant_number,
240 max_pressure: 0.0,
241 total_energy: 0.0,
242 },
243 Task::none(),
244 )
245 }
246
247 pub fn title(&self) -> String {
249 format!(
250 "RingKernel WaveSim - {}x{} Grid ({})",
251 self.grid_width, self.grid_height, self.compute_backend
252 )
253 }
254
255 pub fn update(&mut self, message: Message) -> Task<Message> {
257 match message {
258 Message::ToggleRunning => {
259 self.is_running = !self.is_running;
260 if self.is_running {
262 self.step_accumulator = 0;
263 self.time_accumulator = Duration::ZERO;
264 self.last_stats_update = Instant::now();
265 }
266 }
267
268 Message::Step => {
269 return self.step_simulation();
270 }
271
272 Message::Reset => {
273 self.reset_performance_stats();
274 match &self.engine {
275 SimulationEngine::Cpu(_) => {
276 if let SimulationEngine::Cpu(grid) = &mut self.engine {
277 grid.reset();
278 let pressure_grid = grid.get_pressure_grid();
279 self.max_pressure = grid.max_pressure();
280 self.total_energy = grid.total_energy();
281 self.canvas.update_pressure(pressure_grid);
282 }
283 }
284 SimulationEngine::Gpu(kernel_grid) => {
285 let grid = kernel_grid.clone();
286 return Task::perform(
287 async move {
288 let mut g = grid.lock().await;
289 g.reset();
290 (g.get_pressure_grid(), g.max_pressure(), g.total_energy())
291 },
292 |(pressure, max_p, energy)| {
293 Message::GpuStepCompleted(pressure, max_p, energy)
294 },
295 );
296 }
297 #[cfg(feature = "cuda")]
298 SimulationEngine::CudaPacked(_) => {
299 return self.switch_to_cuda_packed();
301 }
302 SimulationEngine::Switching => {}
303 }
304 }
305
306 Message::SpeedChanged(speed) => {
307 self.speed_of_sound = speed;
308 let params = AcousticParams::new(speed, self.cell_size);
309 #[allow(unused_variables)]
310 let c2 = params.courant_number().powi(2);
311 #[allow(unused_variables)]
312 let damping = 1.0 - params.damping;
313 self.courant_number = params.courant_number();
314
315 match &mut self.engine {
316 SimulationEngine::Cpu(grid) => {
317 grid.set_speed_of_sound(speed);
318 }
319 SimulationEngine::Gpu(kernel_grid) => {
320 let grid = kernel_grid.clone();
321 tokio::spawn(async move {
322 grid.lock().await.set_speed_of_sound(speed);
323 });
324 }
325 #[cfg(feature = "cuda")]
326 SimulationEngine::CudaPacked(backend) => {
327 if let Ok(mut b) = backend.lock() {
328 b.set_params(c2, damping);
329 }
330 }
331 SimulationEngine::Switching => {}
332 }
333 }
334
335 Message::GridWidthChanged(s) => {
336 self.grid_width_input = s;
337 }
338
339 Message::GridHeightChanged(s) => {
340 self.grid_height_input = s;
341 }
342
343 Message::ApplyGridSize => {
344 if let (Ok(w), Ok(h)) = (
345 self.grid_width_input.parse::<u32>(),
346 self.grid_height_input.parse::<u32>(),
347 ) {
348 #[cfg(feature = "cuda")]
350 let max_size = if matches!(self.compute_backend, ComputeBackend::CudaPacked) {
351 512
352 } else {
353 256
354 };
355 #[cfg(not(feature = "cuda"))]
356 let max_size = 256;
357
358 let w = w.clamp(16, max_size);
359 let h = h.clamp(16, max_size);
360 self.grid_width = w;
361 self.grid_height = h;
362 self.grid_width_input = w.to_string();
363 self.grid_height_input = h.to_string();
364 self.cell_count = (w * h) as usize;
365 self.reset_performance_stats();
366
367 return self.switch_backend(self.compute_backend);
369 }
370 }
371
372 Message::ImpulseAmplitudeChanged(amp) => {
373 self.impulse_amplitude = amp;
374 }
375
376 Message::CellSizeChanged(size) => {
377 self.cell_size = size;
378 let params = AcousticParams::new(self.speed_of_sound, size);
379 #[allow(unused_variables)]
380 let c2 = params.courant_number().powi(2);
381 #[allow(unused_variables)]
382 let damping = 1.0 - params.damping;
383 self.courant_number = params.courant_number();
384
385 match &mut self.engine {
386 SimulationEngine::Cpu(grid) => {
387 grid.set_cell_size(size);
388 }
389 SimulationEngine::Gpu(kernel_grid) => {
390 let grid = kernel_grid.clone();
391 tokio::spawn(async move {
392 grid.lock().await.set_cell_size(size);
393 });
394 }
395 #[cfg(feature = "cuda")]
396 SimulationEngine::CudaPacked(backend) => {
397 if let Ok(mut b) = backend.lock() {
398 b.set_params(c2, damping);
399 }
400 }
401 SimulationEngine::Switching => {}
402 }
403 }
404
405 Message::ComputeBackendChanged(new_backend) => {
406 if new_backend == self.compute_backend {
407 return Task::none();
408 }
409 tracing::info!(
410 "Switching compute backend from {} to {}",
411 self.compute_backend,
412 new_backend
413 );
414 self.compute_backend = new_backend;
415 self.reset_performance_stats();
416 return self.switch_backend(new_backend);
417 }
418
419 Message::BackendSwitched(result) => match result {
420 Ok(kernel_grid) => {
421 self.engine = SimulationEngine::Gpu(kernel_grid.clone());
422 return Task::perform(
423 async move {
424 let g = kernel_grid.lock().await;
425 (g.get_pressure_grid(), g.max_pressure(), g.total_energy())
426 },
427 |(pressure, max_p, energy)| {
428 Message::GpuStepCompleted(pressure, max_p, energy)
429 },
430 );
431 }
432 Err(e) => {
433 tracing::error!("Failed to switch backend: {}", e);
434 self.compute_backend = ComputeBackend::Cpu;
435 let params = AcousticParams::new(self.speed_of_sound, self.cell_size);
436 let grid = SimulationGrid::new(self.grid_width, self.grid_height, params);
437 self.cell_count = grid.cell_count();
438 self.courant_number = grid.params.courant_number();
439 let pressure = grid.get_pressure_grid();
440 self.engine = SimulationEngine::Cpu(grid);
441 self.canvas.update_pressure(pressure);
442 }
443 },
444
445 #[cfg(feature = "cuda")]
446 Message::CudaPackedSwitched(result) => {
447 match result {
448 Ok(backend) => {
449 self.engine = SimulationEngine::CudaPacked(backend.clone());
450 let pressure = {
452 let b = backend.lock().unwrap();
453 b.read_pressure_grid()
454 .unwrap_or_else(|_| vec![0.0; self.cell_count])
455 };
456 self.update_canvas_from_flat(&pressure);
457 }
458 Err(e) => {
459 tracing::error!("Failed to create CUDA Packed backend: {}", e);
460 self.compute_backend = ComputeBackend::Cpu;
461 let params = AcousticParams::new(self.speed_of_sound, self.cell_size);
462 let grid = SimulationGrid::new(self.grid_width, self.grid_height, params);
463 self.cell_count = grid.cell_count();
464 self.courant_number = grid.params.courant_number();
465 let pressure = grid.get_pressure_grid();
466 self.engine = SimulationEngine::Cpu(grid);
467 self.canvas.update_pressure(pressure);
468 }
469 }
470 }
471
472 Message::CanvasClick(x, y) => {
473 let gx = (x * self.grid_width as f32) as u32;
474 let gy = (y * self.grid_height as f32) as u32;
475 let gx = gx.min(self.grid_width - 1);
476 let gy = gy.min(self.grid_height - 1);
477
478 match self.draw_mode {
480 DrawMode::Impulse => {
481 match &self.engine {
483 SimulationEngine::Cpu(_) => {
484 if let SimulationEngine::Cpu(grid) = &mut self.engine {
485 grid.inject_impulse(gx, gy, self.impulse_amplitude);
486 let pressure_grid = grid.get_pressure_grid();
487 self.max_pressure = grid.max_pressure();
488 self.total_energy = grid.total_energy();
489 self.canvas.update_pressure(pressure_grid);
490 }
491 }
492 SimulationEngine::Gpu(kernel_grid) => {
493 let grid = kernel_grid.clone();
494 let amp = self.impulse_amplitude;
495 return Task::perform(
496 async move {
497 let mut g = grid.lock().await;
498 g.inject_impulse(gx, gy, amp);
499 (g.get_pressure_grid(), g.max_pressure(), g.total_energy())
500 },
501 |(pressure, max_p, energy)| {
502 Message::GpuStepCompleted(pressure, max_p, energy)
503 },
504 );
505 }
506 #[cfg(feature = "cuda")]
507 SimulationEngine::CudaPacked(backend) => {
508 let backend = backend.clone();
509 let amp = self.impulse_amplitude;
510 return Task::perform(
511 async move {
512 let b = backend.lock().unwrap();
513 let _ = b.inject_impulse(gx, gy, amp);
514 let pressure = b.read_pressure_grid().unwrap_or_default();
515 (pressure, 0u32)
516 },
517 |(pressure, steps)| {
518 Message::CudaPackedStepCompleted(pressure, steps)
519 },
520 );
521 }
522 SimulationEngine::Switching => {}
523 }
524 }
525 DrawMode::Absorber | DrawMode::Reflector | DrawMode::Erase => {
526 let cell_type = match self.draw_mode {
528 DrawMode::Absorber => CellType::Absorber,
529 DrawMode::Reflector => CellType::Reflector,
530 DrawMode::Erase => CellType::Normal,
531 _ => CellType::Normal,
532 };
533 self.set_cell_type_at(gx, gy, cell_type);
534 }
535 }
536 }
537
538 Message::CanvasRightClick(x, y) => {
539 let gx = (x * self.grid_width as f32) as u32;
541 let gy = (y * self.grid_height as f32) as u32;
542 let gx = gx.min(self.grid_width - 1);
543 let gy = gy.min(self.grid_height - 1);
544
545 let cell_type = match self.draw_mode {
546 DrawMode::Impulse => CellType::Normal, DrawMode::Absorber => CellType::Absorber,
548 DrawMode::Reflector => CellType::Reflector,
549 DrawMode::Erase => CellType::Normal,
550 };
551 self.set_cell_type_at(gx, gy, cell_type);
552 }
553
554 Message::DrawModeChanged(mode) => {
555 self.draw_mode = mode;
556 }
557
558 Message::ClearCellTypes => {
559 for row in &mut self.cell_types {
561 for cell in row {
562 *cell = CellType::Normal;
563 }
564 }
565 if let SimulationEngine::Cpu(grid) = &mut self.engine {
567 grid.clear_cell_types();
568 }
569 self.canvas.update_cell_types(self.cell_types.clone());
570 }
571
572 Message::Tick => {
573 if self.is_running {
574 return self.step_simulation();
575 }
576 }
577
578 Message::GpuStepCompleted(pressure_grid, max_pressure, total_energy) => {
579 self.canvas.update_pressure(pressure_grid);
580 self.max_pressure = max_pressure;
581 self.total_energy = total_energy;
582 self.update_frame_stats(self.steps_last_frame);
583 }
584
585 #[cfg(feature = "cuda")]
586 Message::CudaPackedStepCompleted(pressure, steps) => {
587 self.update_canvas_from_flat(&pressure);
588 self.update_frame_stats(steps);
589 }
590
591 Message::ToggleStats => {
592 self.show_stats = !self.show_stats;
593 }
594
595 Message::SimulationModeChanged(mode) => {
596 self.simulation_mode = mode;
597 self.educational_processor.set_mode(mode);
598 tracing::info!("Simulation mode changed to: {}", mode);
599 }
600 }
601
602 Task::none()
603 }
604
605 fn switch_backend(&mut self, backend: ComputeBackend) -> Task<Message> {
607 match backend {
608 ComputeBackend::Cpu => {
609 let params = AcousticParams::new(self.speed_of_sound, self.cell_size);
610 let grid = SimulationGrid::new(self.grid_width, self.grid_height, params);
611 self.cell_count = grid.cell_count();
612 self.courant_number = grid.params.courant_number();
613 let pressure = grid.get_pressure_grid();
614 self.engine = SimulationEngine::Cpu(grid);
615 self.canvas.update_pressure(pressure);
616 Task::none()
617 }
618 ComputeBackend::GpuActor => {
619 self.engine = SimulationEngine::Switching;
620 let width = self.grid_width;
621 let height = self.grid_height;
622 let speed = self.speed_of_sound;
623 let cell_size = self.cell_size;
624 let legacy_backend = self.legacy_backend;
625
626 Task::perform(
627 async move {
628 let params = AcousticParams::new(speed, cell_size);
629 match KernelGrid::new(width, height, params, legacy_backend).await {
630 Ok(grid) => Ok(Arc::new(Mutex::new(grid))),
631 Err(e) => Err(format!("{:?}", e)),
632 }
633 },
634 Message::BackendSwitched,
635 )
636 }
637 #[cfg(feature = "cuda")]
638 ComputeBackend::CudaPacked => self.switch_to_cuda_packed(),
639 }
640 }
641
642 #[cfg(feature = "cuda")]
644 fn switch_to_cuda_packed(&mut self) -> Task<Message> {
645 self.engine = SimulationEngine::Switching;
646 let width = self.grid_width;
647 let height = self.grid_height;
648 let speed = self.speed_of_sound;
649 let cell_size = self.cell_size;
650
651 Task::perform(
652 async move {
653 let params = AcousticParams::new(speed, cell_size);
654 let c2 = params.courant_number().powi(2);
655 let damping = 1.0 - params.damping;
656
657 match CudaPackedBackend::new(width, height, 16) {
658 Ok(mut backend) => {
659 backend.set_params(c2, damping);
660 Ok(Arc::new(std::sync::Mutex::new(backend)))
661 }
662 Err(e) => Err(format!("{:?}", e)),
663 }
664 },
665 Message::CudaPackedSwitched,
666 )
667 }
668
669 fn reset_performance_stats(&mut self) {
671 self.fps = 0.0;
672 self.steps_per_sec = 0.0;
673 self.throughput = 0.0;
674 self.steps_last_frame = 0;
675 self.step_accumulator = 0;
676 self.time_accumulator = Duration::ZERO;
677 self.last_stats_update = Instant::now();
678 }
679
680 fn update_frame_stats(&mut self, steps: u32) {
682 let now = Instant::now();
683 let delta = now.duration_since(self.last_frame);
684 self.fps = 1.0 / delta.as_secs_f32();
685 self.last_frame = now;
686 self.steps_last_frame = steps;
687
688 self.step_accumulator += steps;
690 self.time_accumulator += delta;
691
692 let stats_delta = now.duration_since(self.last_stats_update);
694 if stats_delta >= Duration::from_millis(500) {
695 let secs = self.time_accumulator.as_secs_f64();
696 if secs > 0.0 {
697 self.steps_per_sec = self.step_accumulator as f32 / secs as f32;
698 self.throughput = (self.step_accumulator as f64 * self.cell_count as f64) / secs;
699 }
700 self.step_accumulator = 0;
701 self.time_accumulator = Duration::ZERO;
702 self.last_stats_update = now;
703 }
704 }
705
706 #[allow(dead_code)]
708 fn update_canvas_from_flat(&mut self, pressure: &[f32]) {
709 let mut grid = Vec::with_capacity(self.grid_height as usize);
710 for y in 0..self.grid_height as usize {
711 let start = y * self.grid_width as usize;
712 let end = start + self.grid_width as usize;
713 if end <= pressure.len() {
714 grid.push(pressure[start..end].to_vec());
715 } else {
716 grid.push(vec![0.0; self.grid_width as usize]);
717 }
718 }
719
720 self.max_pressure = pressure.iter().fold(0.0f32, |acc, &p| acc.max(p.abs()));
722 self.total_energy = pressure.iter().map(|&p| p * p).sum();
723
724 self.canvas.update_pressure(grid);
725 }
726
727 fn set_cell_type_at(&mut self, gx: u32, gy: u32, cell_type: CellType) {
729 let gx = gx as usize;
730 let gy = gy as usize;
731
732 let height = self.grid_height as usize;
734 let width = self.grid_width as usize;
735 if self.cell_types.len() != height || (height > 0 && self.cell_types[0].len() != width) {
736 self.cell_types = vec![vec![CellType::Normal; width]; height];
737 }
738
739 if gy < self.cell_types.len() && gx < self.cell_types[gy].len() {
741 self.cell_types[gy][gx] = cell_type;
742 }
743
744 if let SimulationEngine::Cpu(grid) = &mut self.engine {
746 grid.set_cell_type(gx as u32, gy as u32, cell_type);
747 }
748
749 self.canvas.update_cell_types(self.cell_types.clone());
751 }
752
753 fn step_simulation(&mut self) -> Task<Message> {
755 match &self.engine {
756 SimulationEngine::Cpu(_) => {
757 let start = Instant::now();
758 if let SimulationEngine::Cpu(grid) = &mut self.engine {
759 if self.simulation_mode != SimulationMode::Standard {
761 let (pressure, pressure_prev, width, height, c2, damping) =
763 grid.get_buffers_mut();
764
765 let result = self.educational_processor.step_frame(
766 pressure,
767 pressure_prev,
768 width,
769 height,
770 c2,
771 damping,
772 );
773
774 if result.step_complete && result.should_swap {
775 grid.swap_buffers();
776 }
777
778 self.canvas.set_processing_state(
780 self.educational_processor.state.just_processed.clone(),
781 self.educational_processor.state.active_tiles.clone(),
782 self.educational_processor.state.current_row,
783 );
784
785 self.steps_last_frame = if result.step_complete { 1 } else { 0 };
786 } else {
787 let target_frame_time = Duration::from_millis(16);
789 let dt = grid.params.time_step;
790 let max_steps = ((0.01 / dt) as u32).clamp(1, 2000);
791
792 let mut steps = 0u32;
793 while start.elapsed() < target_frame_time && steps < max_steps {
794 grid.step();
795 steps += 1;
796 }
797
798 self.steps_last_frame = steps;
799 self.canvas.set_processing_state(vec![], vec![], None);
801 }
802
803 let pressure_grid = grid.get_pressure_grid();
804 self.max_pressure = grid.max_pressure();
805 self.total_energy = grid.total_energy();
806 self.canvas.update_pressure(pressure_grid);
807 self.update_frame_stats(self.steps_last_frame);
808 }
809 Task::none()
810 }
811 SimulationEngine::Gpu(kernel_grid) => {
812 let grid = kernel_grid.clone();
813
814 Task::perform(
815 async move {
816 let mut g = grid.lock().await;
817 let dt = g.params.time_step;
818 let steps = ((0.01 / dt) as u32).clamp(1, 500);
819
820 for _ in 0..steps {
821 if let Err(e) = g.step().await {
822 tracing::error!("GPU step error: {:?}", e);
823 break;
824 }
825 }
826 (g.get_pressure_grid(), g.max_pressure(), g.total_energy())
827 },
828 |(pressure, max_p, energy)| Message::GpuStepCompleted(pressure, max_p, energy),
829 )
830 }
831 #[cfg(feature = "cuda")]
832 SimulationEngine::CudaPacked(backend) => {
833 let backend = backend.clone();
834 let cell_count = self.cell_count;
835
836 Task::perform(
837 async move {
838 let mut b = backend.lock().unwrap();
839 let steps = 100u32;
841 if let Err(e) = b.step_batch(steps) {
842 tracing::error!("CUDA step error: {:?}", e);
843 }
844 let pressure = b
845 .read_pressure_grid()
846 .unwrap_or_else(|_| vec![0.0; cell_count]);
847 (pressure, steps)
848 },
849 |(pressure, steps)| Message::CudaPackedStepCompleted(pressure, steps),
850 )
851 }
852 SimulationEngine::Switching => Task::none(),
853 }
854 }
855
856 pub fn view(&self) -> Element<'_, Message> {
858 let canvas = Canvas::new(&self.canvas)
859 .width(Length::Fill)
860 .height(Length::Fill);
861
862 let controls = controls::view_controls(
863 self.is_running,
864 self.speed_of_sound,
865 self.cell_size,
866 &self.grid_width_input,
867 &self.grid_height_input,
868 self.impulse_amplitude,
869 self.compute_backend,
870 self.draw_mode,
871 self.simulation_mode,
872 self.show_stats,
873 self.fps,
874 self.steps_per_sec,
875 self.throughput,
876 self.cell_count,
877 self.courant_number,
878 self.max_pressure,
879 self.total_energy,
880 );
881
882 let content = row![
883 container(canvas)
884 .width(Length::FillPortion(3))
885 .height(Length::Fill)
886 .padding(10),
887 container(controls)
888 .width(Length::FillPortion(1))
889 .height(Length::Fill)
890 .padding(10),
891 ];
892
893 container(content)
894 .width(Length::Fill)
895 .height(Length::Fill)
896 .into()
897 }
898
899 pub fn subscription(&self) -> Subscription<Message> {
901 if self.is_running {
902 iced::time::every(Duration::from_millis(16)).map(|_| Message::Tick)
903 } else {
904 Subscription::none()
905 }
906 }
907
908 pub fn theme(&self) -> Theme {
910 Theme::Dark
911 }
912}
913
914impl Default for WaveSimApp {
915 fn default() -> Self {
916 Self::new().0
917 }
918}
919
920pub fn run() -> iced::Result {
922 iced::application(WaveSimApp::title, WaveSimApp::update, WaveSimApp::view)
923 .subscription(WaveSimApp::subscription)
924 .theme(WaveSimApp::theme)
925 .window_size(Size::new(1200.0, 800.0))
926 .antialiasing(true)
927 .run_with(WaveSimApp::new)
928}