use crate::model::{Cartridge, Color, Sample}; use crate::peripherals::{Audio, Gfx, InputSignals, Pad, Touch}; use crate::vm::VirtualMachine; use std::sync::Arc; /// PROMETEU "hardware lógico" (v0). /// O Host alimenta INPUT SIGNALS e chama `step_frame()` em 60Hz. pub struct LogicalHardware { pub gfx: Gfx, pub audio: Audio, pub pad: Pad, pub touch: Touch, pub tick_index: u64, pub logical_frame_index: u64, pub last_frame_cpu_time_us: u64, pub vm: VirtualMachine, pub cartridge: Option, // Estado Interno logical_frame_open: bool, // Assets de exemplo pub sample_square: Option>, pub sample_kick: Option>, pub sample_snare: Option>, } impl LogicalHardware { pub const W: usize = 320; pub const H: usize = 180; pub const CYCLES_PER_FRAME: u64 = 100_000; // Exemplo de budget pub fn new() -> Self { let mut logical_hardware = Self { gfx: Gfx::new(Self::W, Self::H), audio: Audio::new(), pad: Pad::default(), touch: Touch::default(), tick_index: 0, logical_frame_index: 0, last_frame_cpu_time_us: 0, vm: VirtualMachine::default(), cartridge: None, logical_frame_open: false, sample_square: None, sample_kick: None, sample_snare: None, }; // Inicializa samples básicos logical_hardware.sample_square = Some(Arc::new(Self::create_square_sample(440.0, 0.1))); logical_hardware } pub fn get_color(&self, index: usize) -> Color { // Implementação simplificada: se houver bancos, usa a paleta do banco 0 // Caso contrário, usa cores básicas fixas if let Some(bank) = self.gfx.banks[0].as_ref() { bank.palettes[0][index % 16] } else { match index % 16 { 0 => Color::BLACK, 1 => Color::WHITE, 2 => Color::RED, 3 => Color::GREEN, 4 => Color::BLUE, 5 => Color::YELLOW, 6 => Color::CYAN, 7 => Color::INDIGO, 8 => Color::GRAY, _ => Color::BLACK, } } } pub fn is_button_down(&self, id: u32) -> bool { match id { 0 => self.pad.up.down, 1 => self.pad.down.down, 2 => self.pad.left.down, 3 => self.pad.right.down, 4 => self.pad.a.down, 5 => self.pad.b.down, 6 => self.pad.x.down, 7 => self.pad.y.down, 8 => self.pad.l.down, 9 => self.pad.r.down, 10 => self.pad.start.down, 11 => self.pad.select.down, _ => false, } } pub fn load_cartridge(&mut self, cart: Cartridge) { self.cartridge = Some(cart); } fn create_square_sample(freq: f64, duration: f64) -> Sample { let sample_rate = crate::peripherals::OUTPUT_SAMPLE_RATE; let num_samples = (duration * sample_rate as f64) as usize; let mut data = Vec::with_capacity(num_samples); let period = sample_rate as f64 / freq; for i in 0..num_samples { let val = if (i as f64 % period) < (period / 2.0) { 10000 // Volume médio } else { -10000 }; data.push(val); } Sample::new(sample_rate, data) } /// "Contrato de frame" do PROMETEU (Template Method sem loop). /// O Host controla tempo/event loop; o Core define a sequência do frame. pub fn step_frame(&mut self, signals: &InputSignals) { let start = std::time::Instant::now(); self.tick_index += 1; // não importa o frame lógico, o tick sempre incrementa // Se um frame logica estiver aberto evita limpar o input if !self.logical_frame_open { self.logical_frame_open = true; self.begin_frame(signals); } // Executa uma fatia fixa de ciclos (budget do tick real do host) let mut vm = std::mem::take(&mut self.vm); let run = vm .run_budget(Self::CYCLES_PER_FRAME, self) .expect("vm error"); self.vm = vm; // Só “materializa” e apresenta quando o frame lógico fecha if run.reason == crate::vm::LogicalFrameEndingReason::FrameSync { self.gfx.render_all(); self.end_frame(); self.logical_frame_index += 1; // conta frames lógicos apresentados self.logical_frame_open = false; } self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64; } /// Início do frame: zera flags transitórias. pub fn begin_frame(&mut self, signals: &InputSignals) { // Limpa comandos de áudio do frame anterior. // Nota: O Host deve consumir esses comandos ANTES ou DURANTE o step_frame se quiser processá-los. // Como o Host atual consome logo após o step_frame, limpar aqui no início do PRÓXIMO frame está correto. self.audio.clear_commands(); // Flags transitórias do TOUCH devem durar 1 frame. self.touch.begin_frame(signals); // Se você quiser input com pressed/released depois: self.pad.begin_frame(signals); } /// Final do frame: troca buffers. pub fn end_frame(&mut self) { self.gfx.present(); } }