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 let c2 = params.courant_number().powi(2);
310 let damping = 1.0 - params.damping;
311 self.courant_number = params.courant_number();
312
313 match &mut self.engine {
314 SimulationEngine::Cpu(grid) => {
315 grid.set_speed_of_sound(speed);
316 }
317 SimulationEngine::Gpu(kernel_grid) => {
318 let grid = kernel_grid.clone();
319 tokio::spawn(async move {
320 grid.lock().await.set_speed_of_sound(speed);
321 });
322 }
323 #[cfg(feature = "cuda")]
324 SimulationEngine::CudaPacked(backend) => {
325 if let Ok(mut b) = backend.lock() {
326 b.set_params(c2, damping);
327 }
328 }
329 SimulationEngine::Switching => {}
330 }
331 }
332
333 Message::GridWidthChanged(s) => {
334 self.grid_width_input = s;
335 }
336
337 Message::GridHeightChanged(s) => {
338 self.grid_height_input = s;
339 }
340
341 Message::ApplyGridSize => {
342 if let (Ok(w), Ok(h)) = (
343 self.grid_width_input.parse::<u32>(),
344 self.grid_height_input.parse::<u32>(),
345 ) {
346 #[cfg(feature = "cuda")]
348 let max_size = if matches!(self.compute_backend, ComputeBackend::CudaPacked) {
349 512
350 } else {
351 256
352 };
353 #[cfg(not(feature = "cuda"))]
354 let max_size = 256;
355
356 let w = w.clamp(16, max_size);
357 let h = h.clamp(16, max_size);
358 self.grid_width = w;
359 self.grid_height = h;
360 self.grid_width_input = w.to_string();
361 self.grid_height_input = h.to_string();
362 self.cell_count = (w * h) as usize;
363 self.reset_performance_stats();
364
365 return self.switch_backend(self.compute_backend);
367 }
368 }
369
370 Message::ImpulseAmplitudeChanged(amp) => {
371 self.impulse_amplitude = amp;
372 }
373
374 Message::CellSizeChanged(size) => {
375 self.cell_size = size;
376 let params = AcousticParams::new(self.speed_of_sound, size);
377 let c2 = params.courant_number().powi(2);
378 let damping = 1.0 - params.damping;
379 self.courant_number = params.courant_number();
380
381 match &mut self.engine {
382 SimulationEngine::Cpu(grid) => {
383 grid.set_cell_size(size);
384 }
385 SimulationEngine::Gpu(kernel_grid) => {
386 let grid = kernel_grid.clone();
387 tokio::spawn(async move {
388 grid.lock().await.set_cell_size(size);
389 });
390 }
391 #[cfg(feature = "cuda")]
392 SimulationEngine::CudaPacked(backend) => {
393 if let Ok(mut b) = backend.lock() {
394 b.set_params(c2, damping);
395 }
396 }
397 SimulationEngine::Switching => {}
398 }
399 }
400
401 Message::ComputeBackendChanged(new_backend) => {
402 if new_backend == self.compute_backend {
403 return Task::none();
404 }
405 tracing::info!(
406 "Switching compute backend from {} to {}",
407 self.compute_backend,
408 new_backend
409 );
410 self.compute_backend = new_backend;
411 self.reset_performance_stats();
412 return self.switch_backend(new_backend);
413 }
414
415 Message::BackendSwitched(result) => match result {
416 Ok(kernel_grid) => {
417 self.engine = SimulationEngine::Gpu(kernel_grid.clone());
418 return Task::perform(
419 async move {
420 let g = kernel_grid.lock().await;
421 (g.get_pressure_grid(), g.max_pressure(), g.total_energy())
422 },
423 |(pressure, max_p, energy)| {
424 Message::GpuStepCompleted(pressure, max_p, energy)
425 },
426 );
427 }
428 Err(e) => {
429 tracing::error!("Failed to switch backend: {}", e);
430 self.compute_backend = ComputeBackend::Cpu;
431 let params = AcousticParams::new(self.speed_of_sound, self.cell_size);
432 let grid = SimulationGrid::new(self.grid_width, self.grid_height, params);
433 self.cell_count = grid.cell_count();
434 self.courant_number = grid.params.courant_number();
435 let pressure = grid.get_pressure_grid();
436 self.engine = SimulationEngine::Cpu(grid);
437 self.canvas.update_pressure(pressure);
438 }
439 },
440
441 #[cfg(feature = "cuda")]
442 Message::CudaPackedSwitched(result) => {
443 match result {
444 Ok(backend) => {
445 self.engine = SimulationEngine::CudaPacked(backend.clone());
446 let pressure = {
448 let b = backend.lock().unwrap();
449 b.read_pressure_grid()
450 .unwrap_or_else(|_| vec![0.0; self.cell_count])
451 };
452 self.update_canvas_from_flat(&pressure);
453 }
454 Err(e) => {
455 tracing::error!("Failed to create CUDA Packed backend: {}", e);
456 self.compute_backend = ComputeBackend::Cpu;
457 let params = AcousticParams::new(self.speed_of_sound, self.cell_size);
458 let grid = SimulationGrid::new(self.grid_width, self.grid_height, params);
459 self.cell_count = grid.cell_count();
460 self.courant_number = grid.params.courant_number();
461 let pressure = grid.get_pressure_grid();
462 self.engine = SimulationEngine::Cpu(grid);
463 self.canvas.update_pressure(pressure);
464 }
465 }
466 }
467
468 Message::CanvasClick(x, y) => {
469 let gx = (x * self.grid_width as f32) as u32;
470 let gy = (y * self.grid_height as f32) as u32;
471 let gx = gx.min(self.grid_width - 1);
472 let gy = gy.min(self.grid_height - 1);
473
474 match self.draw_mode {
476 DrawMode::Impulse => {
477 match &self.engine {
479 SimulationEngine::Cpu(_) => {
480 if let SimulationEngine::Cpu(grid) = &mut self.engine {
481 grid.inject_impulse(gx, gy, self.impulse_amplitude);
482 let pressure_grid = grid.get_pressure_grid();
483 self.max_pressure = grid.max_pressure();
484 self.total_energy = grid.total_energy();
485 self.canvas.update_pressure(pressure_grid);
486 }
487 }
488 SimulationEngine::Gpu(kernel_grid) => {
489 let grid = kernel_grid.clone();
490 let amp = self.impulse_amplitude;
491 return Task::perform(
492 async move {
493 let mut g = grid.lock().await;
494 g.inject_impulse(gx, gy, amp);
495 (g.get_pressure_grid(), g.max_pressure(), g.total_energy())
496 },
497 |(pressure, max_p, energy)| {
498 Message::GpuStepCompleted(pressure, max_p, energy)
499 },
500 );
501 }
502 #[cfg(feature = "cuda")]
503 SimulationEngine::CudaPacked(backend) => {
504 let backend = backend.clone();
505 let amp = self.impulse_amplitude;
506 return Task::perform(
507 async move {
508 let b = backend.lock().unwrap();
509 let _ = b.inject_impulse(gx, gy, amp);
510 let pressure = b.read_pressure_grid().unwrap_or_default();
511 (pressure, 0u32)
512 },
513 |(pressure, steps)| {
514 Message::CudaPackedStepCompleted(pressure, steps)
515 },
516 );
517 }
518 SimulationEngine::Switching => {}
519 }
520 }
521 DrawMode::Absorber | DrawMode::Reflector | DrawMode::Erase => {
522 let cell_type = match self.draw_mode {
524 DrawMode::Absorber => CellType::Absorber,
525 DrawMode::Reflector => CellType::Reflector,
526 DrawMode::Erase => CellType::Normal,
527 _ => CellType::Normal,
528 };
529 self.set_cell_type_at(gx, gy, cell_type);
530 }
531 }
532 }
533
534 Message::CanvasRightClick(x, y) => {
535 let gx = (x * self.grid_width as f32) as u32;
537 let gy = (y * self.grid_height as f32) as u32;
538 let gx = gx.min(self.grid_width - 1);
539 let gy = gy.min(self.grid_height - 1);
540
541 let cell_type = match self.draw_mode {
542 DrawMode::Impulse => CellType::Normal, DrawMode::Absorber => CellType::Absorber,
544 DrawMode::Reflector => CellType::Reflector,
545 DrawMode::Erase => CellType::Normal,
546 };
547 self.set_cell_type_at(gx, gy, cell_type);
548 }
549
550 Message::DrawModeChanged(mode) => {
551 self.draw_mode = mode;
552 }
553
554 Message::ClearCellTypes => {
555 for row in &mut self.cell_types {
557 for cell in row {
558 *cell = CellType::Normal;
559 }
560 }
561 if let SimulationEngine::Cpu(grid) = &mut self.engine {
563 grid.clear_cell_types();
564 }
565 self.canvas.update_cell_types(self.cell_types.clone());
566 }
567
568 Message::Tick => {
569 if self.is_running {
570 return self.step_simulation();
571 }
572 }
573
574 Message::GpuStepCompleted(pressure_grid, max_pressure, total_energy) => {
575 self.canvas.update_pressure(pressure_grid);
576 self.max_pressure = max_pressure;
577 self.total_energy = total_energy;
578 self.update_frame_stats(self.steps_last_frame);
579 }
580
581 #[cfg(feature = "cuda")]
582 Message::CudaPackedStepCompleted(pressure, steps) => {
583 self.update_canvas_from_flat(&pressure);
584 self.update_frame_stats(steps);
585 }
586
587 Message::ToggleStats => {
588 self.show_stats = !self.show_stats;
589 }
590
591 Message::SimulationModeChanged(mode) => {
592 self.simulation_mode = mode;
593 self.educational_processor.set_mode(mode);
594 tracing::info!("Simulation mode changed to: {}", mode);
595 }
596 }
597
598 Task::none()
599 }
600
601 fn switch_backend(&mut self, backend: ComputeBackend) -> Task<Message> {
603 match backend {
604 ComputeBackend::Cpu => {
605 let params = AcousticParams::new(self.speed_of_sound, self.cell_size);
606 let grid = SimulationGrid::new(self.grid_width, self.grid_height, params);
607 self.cell_count = grid.cell_count();
608 self.courant_number = grid.params.courant_number();
609 let pressure = grid.get_pressure_grid();
610 self.engine = SimulationEngine::Cpu(grid);
611 self.canvas.update_pressure(pressure);
612 Task::none()
613 }
614 ComputeBackend::GpuActor => {
615 self.engine = SimulationEngine::Switching;
616 let width = self.grid_width;
617 let height = self.grid_height;
618 let speed = self.speed_of_sound;
619 let cell_size = self.cell_size;
620 let legacy_backend = self.legacy_backend;
621
622 Task::perform(
623 async move {
624 let params = AcousticParams::new(speed, cell_size);
625 match KernelGrid::new(width, height, params, legacy_backend).await {
626 Ok(grid) => Ok(Arc::new(Mutex::new(grid))),
627 Err(e) => Err(format!("{:?}", e)),
628 }
629 },
630 Message::BackendSwitched,
631 )
632 }
633 #[cfg(feature = "cuda")]
634 ComputeBackend::CudaPacked => self.switch_to_cuda_packed(),
635 }
636 }
637
638 #[cfg(feature = "cuda")]
640 fn switch_to_cuda_packed(&mut self) -> Task<Message> {
641 self.engine = SimulationEngine::Switching;
642 let width = self.grid_width;
643 let height = self.grid_height;
644 let speed = self.speed_of_sound;
645 let cell_size = self.cell_size;
646
647 Task::perform(
648 async move {
649 let params = AcousticParams::new(speed, cell_size);
650 let c2 = params.courant_number().powi(2);
651 let damping = 1.0 - params.damping;
652
653 match CudaPackedBackend::new(width, height, 16) {
654 Ok(mut backend) => {
655 backend.set_params(c2, damping);
656 Ok(Arc::new(std::sync::Mutex::new(backend)))
657 }
658 Err(e) => Err(format!("{:?}", e)),
659 }
660 },
661 Message::CudaPackedSwitched,
662 )
663 }
664
665 fn reset_performance_stats(&mut self) {
667 self.fps = 0.0;
668 self.steps_per_sec = 0.0;
669 self.throughput = 0.0;
670 self.steps_last_frame = 0;
671 self.step_accumulator = 0;
672 self.time_accumulator = Duration::ZERO;
673 self.last_stats_update = Instant::now();
674 }
675
676 fn update_frame_stats(&mut self, steps: u32) {
678 let now = Instant::now();
679 let delta = now.duration_since(self.last_frame);
680 self.fps = 1.0 / delta.as_secs_f32();
681 self.last_frame = now;
682 self.steps_last_frame = steps;
683
684 self.step_accumulator += steps;
686 self.time_accumulator += delta;
687
688 let stats_delta = now.duration_since(self.last_stats_update);
690 if stats_delta >= Duration::from_millis(500) {
691 let secs = self.time_accumulator.as_secs_f64();
692 if secs > 0.0 {
693 self.steps_per_sec = self.step_accumulator as f32 / secs as f32;
694 self.throughput = (self.step_accumulator as f64 * self.cell_count as f64) / secs;
695 }
696 self.step_accumulator = 0;
697 self.time_accumulator = Duration::ZERO;
698 self.last_stats_update = now;
699 }
700 }
701
702 fn update_canvas_from_flat(&mut self, pressure: &[f32]) {
704 let mut grid = Vec::with_capacity(self.grid_height as usize);
705 for y in 0..self.grid_height as usize {
706 let start = y * self.grid_width as usize;
707 let end = start + self.grid_width as usize;
708 if end <= pressure.len() {
709 grid.push(pressure[start..end].to_vec());
710 } else {
711 grid.push(vec![0.0; self.grid_width as usize]);
712 }
713 }
714
715 self.max_pressure = pressure.iter().fold(0.0f32, |acc, &p| acc.max(p.abs()));
717 self.total_energy = pressure.iter().map(|&p| p * p).sum();
718
719 self.canvas.update_pressure(grid);
720 }
721
722 fn set_cell_type_at(&mut self, gx: u32, gy: u32, cell_type: CellType) {
724 let gx = gx as usize;
725 let gy = gy as usize;
726
727 let height = self.grid_height as usize;
729 let width = self.grid_width as usize;
730 if self.cell_types.len() != height || (height > 0 && self.cell_types[0].len() != width) {
731 self.cell_types = vec![vec![CellType::Normal; width]; height];
732 }
733
734 if gy < self.cell_types.len() && gx < self.cell_types[gy].len() {
736 self.cell_types[gy][gx] = cell_type;
737 }
738
739 if let SimulationEngine::Cpu(grid) = &mut self.engine {
741 grid.set_cell_type(gx as u32, gy as u32, cell_type);
742 }
743
744 self.canvas.update_cell_types(self.cell_types.clone());
746 }
747
748 fn step_simulation(&mut self) -> Task<Message> {
750 match &self.engine {
751 SimulationEngine::Cpu(_) => {
752 let start = Instant::now();
753 if let SimulationEngine::Cpu(grid) = &mut self.engine {
754 if self.simulation_mode != SimulationMode::Standard {
756 let (pressure, pressure_prev, width, height, c2, damping) =
758 grid.get_buffers_mut();
759
760 let result = self.educational_processor.step_frame(
761 pressure,
762 pressure_prev,
763 width,
764 height,
765 c2,
766 damping,
767 );
768
769 if result.step_complete && result.should_swap {
770 grid.swap_buffers();
771 }
772
773 self.canvas.set_processing_state(
775 self.educational_processor.state.just_processed.clone(),
776 self.educational_processor.state.active_tiles.clone(),
777 self.educational_processor.state.current_row,
778 );
779
780 self.steps_last_frame = if result.step_complete { 1 } else { 0 };
781 } else {
782 let target_frame_time = Duration::from_millis(16);
784 let dt = grid.params.time_step;
785 let max_steps = ((0.01 / dt) as u32).clamp(1, 2000);
786
787 let mut steps = 0u32;
788 while start.elapsed() < target_frame_time && steps < max_steps {
789 grid.step();
790 steps += 1;
791 }
792
793 self.steps_last_frame = steps;
794 self.canvas.set_processing_state(vec![], vec![], None);
796 }
797
798 let pressure_grid = grid.get_pressure_grid();
799 self.max_pressure = grid.max_pressure();
800 self.total_energy = grid.total_energy();
801 self.canvas.update_pressure(pressure_grid);
802 self.update_frame_stats(self.steps_last_frame);
803 }
804 Task::none()
805 }
806 SimulationEngine::Gpu(kernel_grid) => {
807 let grid = kernel_grid.clone();
808
809 Task::perform(
810 async move {
811 let mut g = grid.lock().await;
812 let dt = g.params.time_step;
813 let steps = ((0.01 / dt) as u32).clamp(1, 500);
814
815 for _ in 0..steps {
816 if let Err(e) = g.step().await {
817 tracing::error!("GPU step error: {:?}", e);
818 break;
819 }
820 }
821 (g.get_pressure_grid(), g.max_pressure(), g.total_energy())
822 },
823 |(pressure, max_p, energy)| Message::GpuStepCompleted(pressure, max_p, energy),
824 )
825 }
826 #[cfg(feature = "cuda")]
827 SimulationEngine::CudaPacked(backend) => {
828 let backend = backend.clone();
829 let cell_count = self.cell_count;
830
831 Task::perform(
832 async move {
833 let mut b = backend.lock().unwrap();
834 let steps = 100u32;
836 if let Err(e) = b.step_batch(steps) {
837 tracing::error!("CUDA step error: {:?}", e);
838 }
839 let pressure = b
840 .read_pressure_grid()
841 .unwrap_or_else(|_| vec![0.0; cell_count]);
842 (pressure, steps)
843 },
844 |(pressure, steps)| Message::CudaPackedStepCompleted(pressure, steps),
845 )
846 }
847 SimulationEngine::Switching => Task::none(),
848 }
849 }
850
851 pub fn view(&self) -> Element<'_, Message> {
853 let canvas = Canvas::new(&self.canvas)
854 .width(Length::Fill)
855 .height(Length::Fill);
856
857 let controls = controls::view_controls(
858 self.is_running,
859 self.speed_of_sound,
860 self.cell_size,
861 &self.grid_width_input,
862 &self.grid_height_input,
863 self.impulse_amplitude,
864 self.compute_backend,
865 self.draw_mode,
866 self.simulation_mode,
867 self.show_stats,
868 self.fps,
869 self.steps_per_sec,
870 self.throughput,
871 self.cell_count,
872 self.courant_number,
873 self.max_pressure,
874 self.total_energy,
875 );
876
877 let content = row![
878 container(canvas)
879 .width(Length::FillPortion(3))
880 .height(Length::Fill)
881 .padding(10),
882 container(controls)
883 .width(Length::FillPortion(1))
884 .height(Length::Fill)
885 .padding(10),
886 ];
887
888 container(content)
889 .width(Length::Fill)
890 .height(Length::Fill)
891 .into()
892 }
893
894 pub fn subscription(&self) -> Subscription<Message> {
896 if self.is_running {
897 iced::time::every(Duration::from_millis(16)).map(|_| Message::Tick)
898 } else {
899 Subscription::none()
900 }
901 }
902
903 pub fn theme(&self) -> Theme {
905 Theme::Dark
906 }
907}
908
909impl Default for WaveSimApp {
910 fn default() -> Self {
911 Self::new().0
912 }
913}
914
915pub fn run() -> iced::Result {
917 iced::application(WaveSimApp::title, WaveSimApp::update, WaveSimApp::view)
918 .subscription(WaveSimApp::subscription)
919 .theme(WaveSimApp::theme)
920 .window_size(Size::new(1200.0, 800.0))
921 .antialiasing(true)
922 .run_with(WaveSimApp::new)
923}