a deliberately stupid space heater that wastes electricity on fire shaders and prime numbers
at main 197 lines 6.3 kB view raw
1use std::sync::atomic::{AtomicBool, AtomicU64, Ordering}; 2use std::sync::Arc; 3use std::time::Instant; 4 5use winit::application::ApplicationHandler; 6use winit::dpi::PhysicalSize; 7use winit::event::WindowEvent; 8use winit::event_loop::ActiveEventLoop; 9use winit::window::{Window, WindowId}; 10 11use crate::cpu; 12use crate::gpu::GpuState; 13 14pub struct App { 15 window: Option<Arc<Window>>, 16 gpu: Option<GpuState>, 17 running: Arc<AtomicBool>, 18 primes_found: Arc<AtomicU64>, 19 numbers_tested: Arc<AtomicU64>, 20 cpu_threads: Vec<std::thread::JoinHandle<()>>, 21 frame_count: u64, 22 last_fps_time: Instant, 23 fps: f64, 24 last_tested_count: u64, 25 tests_per_sec: f64, 26} 27 28impl App { 29 pub fn new() -> Self { 30 Self { 31 window: None, 32 gpu: None, 33 running: Arc::new(AtomicBool::new(true)), 34 primes_found: Arc::new(AtomicU64::new(0)), 35 numbers_tested: Arc::new(AtomicU64::new(0)), 36 cpu_threads: Vec::new(), 37 frame_count: 0, 38 last_fps_time: Instant::now(), 39 fps: 0.0, 40 last_tested_count: 0, 41 tests_per_sec: 0.0, 42 } 43 } 44 45 fn spawn_cpu_workers(&mut self) { 46 let num_threads = num_cpus::get(); 47 log::info!("Spawning {} CPU threads for prime search", num_threads); 48 49 for i in 0..num_threads { 50 let running = self.running.clone(); 51 let primes_found = self.primes_found.clone(); 52 let numbers_tested = self.numbers_tested.clone(); 53 let handle = std::thread::Builder::new() 54 .name(format!("prime-worker-{}", i)) 55 .spawn(move || { 56 cpu::prime_worker( 57 running, 58 primes_found, 59 numbers_tested, 60 i as u64, 61 num_threads as u64, 62 ); 63 }) 64 .expect("Failed to spawn CPU worker thread"); 65 self.cpu_threads.push(handle); 66 } 67 } 68 69 fn update_title(&mut self) { 70 self.frame_count += 1; 71 let now = Instant::now(); 72 let elapsed = now.duration_since(self.last_fps_time).as_secs_f64(); 73 74 let current_tested = self.numbers_tested.load(Ordering::Relaxed); 75 let current_primes = self.primes_found.load(Ordering::Relaxed); 76 77 if elapsed >= 0.5 { 78 self.fps = self.frame_count as f64 / elapsed; 79 let delta = current_tested.saturating_sub(self.last_tested_count); 80 self.tests_per_sec = delta as f64 / elapsed; 81 self.last_tested_count = current_tested; 82 self.frame_count = 0; 83 self.last_fps_time = now; 84 } 85 86 let num_cores = num_cpus::get(); 87 88 let title = format!( 89 "\u{1F525} HEATSLOP \u{2014} GPU: {:.0} fps | CPU: {} cores \u{00D7} primes \u{2014} {} found, {} tests/s | Stay warm!", 90 self.fps, 91 num_cores, 92 format_number(current_primes), 93 format_number(self.tests_per_sec as u64), 94 ); 95 96 if let Some(window) = &self.window { 97 window.set_title(&title); 98 } 99 } 100} 101 102fn format_number(n: u64) -> String { 103 if n >= 1_000_000_000 { 104 format!("{:.2}B", n as f64 / 1_000_000_000.0) 105 } else if n >= 1_000_000 { 106 format!("{:.2}M", n as f64 / 1_000_000.0) 107 } else if n >= 1_000 { 108 format!("{:.1}K", n as f64 / 1_000.0) 109 } else { 110 format!("{}", n) 111 } 112} 113 114impl ApplicationHandler for App { 115 fn resumed(&mut self, event_loop: &ActiveEventLoop) { 116 if self.window.is_some() { 117 return; 118 } 119 120 let window_attrs = Window::default_attributes() 121 .with_title("\u{1F525} HEATSLOP \u{2014} Starting up...") 122 .with_inner_size(PhysicalSize::new(1280u32, 720u32)); 123 124 let window = Arc::new(event_loop.create_window(window_attrs).unwrap()); 125 126 let gpu = pollster::block_on(GpuState::new(window.clone())); 127 128 self.window = Some(window); 129 self.gpu = Some(gpu); 130 131 // Start CPU workers 132 self.spawn_cpu_workers(); 133 } 134 135 fn window_event( 136 &mut self, 137 event_loop: &ActiveEventLoop, 138 _window_id: WindowId, 139 event: WindowEvent, 140 ) { 141 match event { 142 WindowEvent::CloseRequested => { 143 log::info!("Shutting down..."); 144 self.running.store(false, Ordering::Relaxed); 145 event_loop.exit(); 146 } 147 WindowEvent::Resized(new_size) => { 148 if let Some(gpu) = &mut self.gpu { 149 gpu.resize(new_size); 150 } 151 } 152 WindowEvent::RedrawRequested => { 153 self.update_title(); 154 155 if let Some(gpu) = &mut self.gpu { 156 match gpu.render() { 157 Ok(_) => {} 158 Err(wgpu::SurfaceError::Lost) => { 159 let size = gpu.size; 160 gpu.resize(size); 161 } 162 Err(wgpu::SurfaceError::OutOfMemory) => { 163 log::error!("Out of GPU memory!"); 164 event_loop.exit(); 165 } 166 Err(e) => { 167 log::warn!("Render error: {:?}", e); 168 } 169 } 170 } 171 172 // Request another frame immediately (no vsync = max GPU throughput) 173 if let Some(window) = &self.window { 174 window.request_redraw(); 175 } 176 } 177 WindowEvent::KeyboardInput { event, .. } => { 178 if event.physical_key 179 == winit::keyboard::PhysicalKey::Code(winit::keyboard::KeyCode::Escape) 180 { 181 self.running.store(false, Ordering::Relaxed); 182 event_loop.exit(); 183 } 184 } 185 _ => {} 186 } 187 } 188} 189 190impl Drop for App { 191 fn drop(&mut self) { 192 self.running.store(false, Ordering::Relaxed); 193 for handle in self.cpu_threads.drain(..) { 194 let _ = handle.join(); 195 } 196 } 197}