use crate::audio::HostAudio; use crate::fs_backend::HostDirBackend; use crate::log_sink::HostConsoleSink; use crate::debugger::HostDebugger; use crate::stats::HostStats; use crate::input::HostInputHandler; use crate::utilities::draw_rgb565_to_rgba8; use pixels::{Pixels, SurfaceTexture}; use prometeu_core::firmware::{BootTarget, Firmware}; use prometeu_core::Hardware; use std::time::{Duration, Instant}; use winit::application::ApplicationHandler; use winit::dpi::LogicalSize; use winit::event::{ElementState, WindowEvent}; use winit::event_loop::{ActiveEventLoop, ControlFlow}; use winit::keyboard::{KeyCode, PhysicalKey}; use winit::window::{Window, WindowAttributes, WindowId}; use prometeu_core::telemetry::CertificationConfig; pub struct HostRunner { window: Option<&'static Window>, pixels: Option>, hardware: Hardware, firmware: Firmware, input: HostInputHandler, fs_root: Option, log_sink: HostConsoleSink, frame_target_dt: Duration, last_frame_time: Instant, accumulator: Duration, stats: HostStats, debugger: HostDebugger, overlay_enabled: bool, audio: HostAudio, } impl HostRunner { pub(crate) fn set_boot_target(&mut self, boot_target: BootTarget) { self.firmware.boot_target = boot_target.clone(); self.debugger.setup_boot_target(&boot_target, &mut self.firmware); } pub(crate) fn new(fs_root: Option, cap_config: Option) -> Self { let target_fps = 60; let mut firmware = Firmware::new(cap_config); if let Some(root) = &fs_root { let backend = HostDirBackend::new(root); firmware.os.mount_fs(Box::new(backend)); } Self { window: None, pixels: None, hardware: Hardware::new(), firmware, input: HostInputHandler::new(), fs_root, log_sink: HostConsoleSink::new(), frame_target_dt: Duration::from_nanos(1_000_000_000 / target_fps), last_frame_time: Instant::now(), accumulator: Duration::ZERO, stats: HostStats::new(), debugger: HostDebugger::new(), overlay_enabled: false, audio: HostAudio::new(), } } fn window(&self) -> &'static Window { self.window.expect("window not created yet") } fn resize_surface(&mut self, width: u32, height: u32) { if let Some(p) = self.pixels.as_mut() { let _ = p.resize_surface(width, height); } } fn request_redraw(&self) { if let Some(w) = self.window.as_ref() { w.request_redraw(); } } fn display_dbg_overlay(&mut self) { let tel = &self.firmware.os.telemetry_last; let color_text = prometeu_core::model::Color::WHITE; let color_bg = prometeu_core::model::Color::INDIGO; // Azul escuro para destacar let color_warn = prometeu_core::model::Color::RED; self.hardware.gfx.fill_rect(5, 5, 140, 65, color_bg); self.hardware.gfx.draw_text(10, 10, &format!("FPS: {:.1}", self.stats.current_fps), color_text); self.hardware.gfx.draw_text(10, 18, &format!("HOST: {:.2}MS", tel.host_cpu_time_us as f64 / 1000.0), color_text); self.hardware.gfx.draw_text(10, 26, &format!("STEPS: {}", tel.vm_steps), color_text); self.hardware.gfx.draw_text(10, 34, &format!("SYSC: {}", tel.syscalls), color_text); self.hardware.gfx.draw_text(10, 42, &format!("CYC: {}", tel.cycles_used), color_text); let cert_color = if tel.violations > 0 { color_warn } else { color_text }; self.hardware.gfx.draw_text(10, 50, &format!("CERT LAST: {}", tel.violations), cert_color); if tel.violations > 0 { if let Some(event) = self.firmware.os.log_service.get_recent(10).into_iter().rev().find(|e| e.tag >= 0xCA01 && e.tag <= 0xCA03) { let mut msg = event.msg.clone(); if msg.len() > 30 { msg.truncate(30); } self.hardware.gfx.draw_text(10, 58, &msg, color_warn); } } } } impl ApplicationHandler for HostRunner { fn resumed(&mut self, event_loop: &ActiveEventLoop) { let attrs = WindowAttributes::default() .with_title(format!( "PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Load: {:.1}% (C) + {:.1}% (A) | Frame: tick {} logical {}", 0.0, 0.0, 0.0, 0, 0, 0)) .with_inner_size(LogicalSize::new(960.0, 540.0)) .with_min_inner_size(LogicalSize::new(320.0, 180.0)); let window = event_loop.create_window(attrs).expect("failed to create window"); // 🔥 Leak: Window vira &'static Window (bootstrap) let window: &'static Window = Box::leak(Box::new(window)); self.window = Some(window); let size = window.inner_size(); let surface_texture = SurfaceTexture::new(size.width, size.height, window); let mut pixels = Pixels::new(Hardware::W as u32, Hardware::H as u32, surface_texture) .expect("failed to create Pixels"); pixels.frame_mut().fill(0); self.pixels = Some(pixels); self.audio.init(); event_loop.set_control_flow(ControlFlow::Poll); } fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) { self.input.handle_event(&event, self.window()); match event { WindowEvent::CloseRequested => event_loop.exit(), WindowEvent::Resized(size) => { self.resize_surface(size.width, size.height); } WindowEvent::ScaleFactorChanged { .. } => { let size = self.window().inner_size(); self.resize_surface(size.width, size.height); } WindowEvent::RedrawRequested => { // Pegue o Pixels diretamente do campo (não via helper que pega &mut self inteiro) let pixels = self.pixels.as_mut().expect("pixels not initialized"); { // Borrow mutável do frame (dura só dentro deste bloco) let frame = pixels.frame_mut(); // Borrow imutável do prometeu-core (campo diferente, ok) let src = self.hardware.gfx.front_buffer(); draw_rgb565_to_rgba8(src, frame); } // <- frame borrow termina aqui if pixels.render().is_err() { event_loop.exit(); } } WindowEvent::KeyboardInput { event, .. } => { if let PhysicalKey::Code(code) = event.physical_key { let is_down = event.state == ElementState::Pressed; if is_down && code == KeyCode::KeyD && self.debugger.waiting_for_start { self.debugger.waiting_for_start = false; println!("[Debugger] Execution started!"); } if is_down && code == KeyCode::F1 { self.overlay_enabled = !self.overlay_enabled; } } } _ => {} } } fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) { self.debugger.check_commands(&mut self.firmware, &mut self.hardware); // Atualiza estado do Filesystem no OS (específico do host-desktop) if let Some(root) = &self.fs_root { use prometeu_core::fs::FsState; if matches!(self.firmware.os.fs_state, FsState::Unmounted | FsState::Error(_)) { if std::path::Path::new(root).exists() { let backend = HostDirBackend::new(root); self.firmware.os.mount_fs(Box::new(backend)); } } } let now = Instant::now(); let mut frame_delta = now.duration_since(self.last_frame_time); // Limitador para evitar a "espiral da morte" se o SO travar (máximo de 100ms por volta) if frame_delta > Duration::from_millis(100) { frame_delta = Duration::from_millis(100); } self.last_frame_time = now; self.accumulator += frame_delta; // 🔥 O coração do determinismo: consome o tempo em fatias exatas de 60Hz while self.accumulator >= self.frame_target_dt { if !self.debugger.waiting_for_start { self.firmware.step_frame(&self.input.signals, &mut self.hardware); } self.audio.send_commands(&mut self.hardware.audio.commands); self.accumulator -= self.frame_target_dt; self.stats.record_frame(); } self.audio.update_stats(&mut self.stats); // Atualiza estatísticas a cada 1 segundo real self.stats.update(now, self.window, &self.hardware, &self.firmware); // Processa logs do sistema let last_seq = self.log_sink.last_seq().unwrap_or(u64::MAX); let new_events = if last_seq == u64::MAX { self.firmware.os.log_service.get_recent(4096) } else { self.firmware.os.log_service.get_after(last_seq) }; self.log_sink.process_events(new_events); // Overlay de Telemetria if self.overlay_enabled { self.hardware.gfx.present(); // Traz o front para o back para desenhar por cima self.display_dbg_overlay(); self.hardware.gfx.present(); // Devolve para o front com o overlay aplicado } self.request_redraw(); } } #[cfg(test)] mod tests { use super::*; use prometeu_core::firmware::BootTarget; use prometeu_core::debugger_protocol::DEVTOOLS_PROTOCOL_VERSION; use std::net::TcpStream; use std::io::{Read, Write}; #[test] fn test_debug_port_opens() { let mut runner = HostRunner::new(None, None); let port = 9999; runner.set_boot_target(BootTarget::Cartridge { path: "dummy.bin".to_string(), debug: true, debug_port: port, }); assert!(runner.debugger.waiting_for_start); assert!(runner.debugger.listener.is_some()); // Verifica se conseguimos conectar { let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Deve conectar"); // Pequeno sleep para garantir que o SO processe std::thread::sleep(std::time::Duration::from_millis(100)); // Simula o loop para aceitar a conexão runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); assert!(runner.debugger.stream.is_some(), "Stream deve ter sido mantido aberto"); // Verifica Handshake let mut buf = [0u8; 2048]; let n = stream.read(&mut buf).expect("Deve ler handshake"); let resp: serde_json::Value = serde_json::from_slice(&buf[..n]).expect("Handshake deve ser JSON válido"); assert_eq!(resp["type"], "handshake"); assert_eq!(resp["protocol_version"], DEVTOOLS_PROTOCOL_VERSION); // Envia start via JSON stream.write_all(b"{\"type\":\"start\"}\n").expect("Conexão deve estar aberta para escrita"); std::thread::sleep(std::time::Duration::from_millis(50)); // Processa o comando recebido runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); assert!(!runner.debugger.waiting_for_start, "Execução deve ter iniciado após comando start"); assert!(runner.debugger.listener.is_some(), "Listener deve permanecer aberto para reconexões"); } // Agora que o stream saiu do escopo do teste, o runner deve detectar o fechamento no próximo check std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); assert!(runner.debugger.stream.is_none(), "Stream deve ter sido fechado após o cliente desconectar"); } #[test] fn test_debug_reconnection() { let mut runner = HostRunner::new(None, None); let port = 9998; runner.set_boot_target(BootTarget::Cartridge { path: "dummy.bin".to_string(), debug: true, debug_port: port, }); // 1. Conecta e inicia { let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Deve conectar 1"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); assert!(runner.debugger.stream.is_some()); stream.write_all(b"{\"type\":\"start\"}\n").expect("Deve escrever start"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); assert!(!runner.debugger.waiting_for_start); // Atualmente o listener é fechado aqui. } // 2. Desconecta (limpa o stream no runner) std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); assert!(runner.debugger.stream.is_none()); // 3. Tenta reconectar - DEVE FALHAR atualmente, mas queremos que FUNCIONE let stream2 = TcpStream::connect(format!("127.0.0.1:{}", port)); assert!(stream2.is_ok(), "Deve aceitar nova conexão mesmo após o start"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); assert!(runner.debugger.stream.is_some(), "Stream deve ter sido aceito na reconexão"); } #[test] fn test_debug_refuse_second_connection() { let mut runner = HostRunner::new(None, None); let port = 9997; runner.set_boot_target(BootTarget::Cartridge { path: "dummy.bin".to_string(), debug: true, debug_port: port, }); // 1. Primeira conexão let mut _stream1 = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Deve conectar 1"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); assert!(runner.debugger.stream.is_some()); // 2. Segunda conexão let mut stream2 = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Deve conectar 2 (SO aceita)"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); // Deve aceitar e fechar stream2 // Verifica se stream2 foi fechado pelo servidor let mut buf = [0u8; 10]; stream2.set_read_timeout(Some(std::time::Duration::from_millis(100))).unwrap(); let res = stream2.read(&mut buf); assert!(matches!(res, Ok(0)) || res.is_err(), "Segunda conexão deve ser fechada pelo servidor"); assert!(runner.debugger.stream.is_some(), "Primeira conexão deve continuar ativa"); } #[test] fn test_get_state_returns_response() { let mut runner = HostRunner::new(None, None); let port = 9996; runner.set_boot_target(BootTarget::Cartridge { path: "dummy.bin".to_string(), debug: true, debug_port: port, }); let stream = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Deve conectar"); std::thread::sleep(std::time::Duration::from_millis(100)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); use std::io::BufRead; let mut reader = std::io::BufReader::new(stream); let mut line = String::new(); reader.read_line(&mut line).expect("Deve ler handshake"); assert!(line.contains("handshake")); // Envia getState reader.get_mut().write_all(b"{\"type\":\"getState\"}\n").expect("Deve escrever getState"); std::thread::sleep(std::time::Duration::from_millis(100)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); // Verifica se recebeu resposta (pode haver eventos/logs antes) loop { line.clear(); reader.read_line(&mut line).expect("Deve ler linha"); if line.is_empty() { break; } if let Ok(resp) = serde_json::from_str::(&line) { if resp["type"] == "getState" { return; } } } panic!("Não recebeu resposta getState"); } #[test] fn test_debug_resume_on_disconnect() { let mut runner = HostRunner::new(None, None); let port = 9995; runner.set_boot_target(BootTarget::Cartridge { path: "dummy.bin".to_string(), debug: true, debug_port: port, }); // 1. Conecta e pausa { let mut stream = TcpStream::connect(format!("127.0.0.1:{}", port)).expect("Deve conectar"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); stream.write_all(b"{\"type\":\"pause\"}\n").expect("Deve escrever pause"); std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); assert!(runner.firmware.os.paused, "VM deve estar pausada"); } // 2. Desconecta (stream sai de escopo) std::thread::sleep(std::time::Duration::from_millis(50)); runner.debugger.check_commands(&mut runner.firmware, &mut runner.hardware); // 3. Verifica se despausou assert!(!runner.firmware.os.paused, "VM deve ter despausado após desconexão"); assert!(!runner.debugger.waiting_for_start, "VM deve ter saído do estado waiting_for_start"); } }