diff --git a/crates/core/src/firmware/firmware.rs b/crates/core/src/firmware/firmware.rs new file mode 100644 index 00000000..43b50721 --- /dev/null +++ b/crates/core/src/firmware/firmware.rs @@ -0,0 +1,77 @@ +use crate::firmware::firmware_state::FirmwareState; +use crate::model::{AppMode, Cartridge, Color}; +use crate::peripherals::InputSignals; +use crate::prometeu_hub::PrometeuHub; +use crate::prometeu_os::PrometeuOS; + +pub struct Firmware { + pub os: PrometeuOS, + pub hub: PrometeuHub, + pub state: FirmwareState, +} + +impl Firmware { + pub fn new() -> Self { + Self { + os: PrometeuOS::new(), + hub: PrometeuHub::new(), + state: FirmwareState::Reset, + } + } + + pub fn step_frame(&mut self, signals: &InputSignals) { + match &mut self.state { + FirmwareState::Reset => { + self.os.reset(); + self.state = FirmwareState::SplashScreen; + } + FirmwareState::SplashScreen => { + // Splash logic opcional + self.state = FirmwareState::LaunchHubHome; + } + FirmwareState::LaunchHubHome => { + self.hub.init(); + self.state = FirmwareState::HubHome; + } + FirmwareState::HubHome => { + // UI do Hub + self.hub.update(&mut self.os); + } + FirmwareState::PosRunGame(_cart) => { + if let Some(error) = self.os.step_frame(signals) { + self.state = FirmwareState::PosCrashScreen(error); + } + } + FirmwareState::PosRunSystem(_cart) => { + // System Apps rodam "dentro" do Hub/Window System + if let Some(error) = self.os.step_frame(signals) { + self.state = FirmwareState::PosCrashScreen(error); + } + // TODO: Compor com a UI do Hub + } + FirmwareState::PosCrashScreen(_error) => { + // Atualiza periféricos para input na tela de crash + self.os.hardware_mut().pad_mut().begin_frame(signals); + + // Tela de erro: fundo vermelho, texto branco + self.os.hardware_mut().gfx_mut().clear(Color::RED); + // Por enquanto apenas logamos ou mostramos algo simples + // No futuro, usar draw_text + self.os.hardware_mut().gfx_mut().present(); + + // Se apertar START, volta pro Hub + if self.os.hardware().pad().start.down { + self.state = FirmwareState::LaunchHubHome; + } + } + } + } + + pub fn load_app(&mut self, cart: Cartridge, mode: AppMode) { + self.os.load_cartridge(&cart); + match mode { + AppMode::Game => self.state = FirmwareState::PosRunGame(cart), + AppMode::System => self.state = FirmwareState::PosRunSystem(cart), + } + } +} \ No newline at end of file diff --git a/crates/core/src/firmware/firmware_state.rs b/crates/core/src/firmware/firmware_state.rs new file mode 100644 index 00000000..1066bbb9 --- /dev/null +++ b/crates/core/src/firmware/firmware_state.rs @@ -0,0 +1,12 @@ +use crate::model::Cartridge; + +#[derive(Debug, Clone)] +pub enum FirmwareState { + Reset, + SplashScreen, + LaunchHubHome, + HubHome, + PosRunGame(Cartridge), + PosRunSystem(Cartridge), + PosCrashScreen(String), +} \ No newline at end of file diff --git a/crates/core/src/firmware/mod.rs b/crates/core/src/firmware/mod.rs new file mode 100644 index 00000000..026f7bec --- /dev/null +++ b/crates/core/src/firmware/mod.rs @@ -0,0 +1,4 @@ +mod firmware; +pub mod firmware_state; + +pub use firmware::Firmware; diff --git a/crates/core/src/fw/firmware.rs b/crates/core/src/fw/firmware.rs deleted file mode 100644 index 3032a967..00000000 --- a/crates/core/src/fw/firmware.rs +++ /dev/null @@ -1,11 +0,0 @@ -pub struct Firmware { - -} - -impl Firmware { - pub fn new() -> Self { - Self { - - } - } -} \ No newline at end of file diff --git a/crates/core/src/fw/mod.rs b/crates/core/src/fw/mod.rs deleted file mode 100644 index 75b0b1cc..00000000 --- a/crates/core/src/fw/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod firmware; \ No newline at end of file diff --git a/crates/core/src/hardware.rs b/crates/core/src/hardware.rs new file mode 100644 index 00000000..ffc738b4 --- /dev/null +++ b/crates/core/src/hardware.rs @@ -0,0 +1,57 @@ +use crate::peripherals::{Audio, Gfx, InputSignals, Pad, Touch, HardwareBridge}; +use crate::firmware::{Firmware}; + +pub struct Hardware { + pub firmware: Firmware, + pub gfx: Gfx, + pub audio: Audio, + pub pad: Pad, + pub touch: Touch, +} + +impl HardwareBridge for Hardware { + fn gfx(&self) -> &Gfx { &self.gfx } + fn gfx_mut(&mut self) -> &mut Gfx { &mut self.gfx } + + fn audio(&self) -> &Audio { &self.audio } + fn audio_mut(&mut self) -> &mut Audio { &mut self.audio } + + fn pad(&self) -> &Pad { &self.pad } + fn pad_mut(&mut self) -> &mut Pad { &mut self.pad } + + fn touch(&self) -> &Touch { &self.touch } + fn touch_mut(&mut self) -> &mut Touch { &mut self.touch } +} + +impl Hardware { + pub const W: usize = 320; + pub const H: usize = 180; + + pub fn new() -> Box { + let mut hw = Box::new(Self { + firmware: Firmware::new(), + gfx: Gfx::new(Self::W, Self::H), + audio: Audio::new(), + pad: Pad::default(), + touch: Touch::default(), + }); + + // Configura a referência circular (através do trait HardwareBridge) + let hw_ptr = hw.as_mut() as *mut dyn HardwareBridge; + hw.firmware.os.hw_ptr = Some(hw_ptr); + + hw + } + + /// Atalhos para o Runner (desktop) não quebrar tanto, + /// delegando para o PrometeuOS onde os contadores agora vivem. + pub fn tick_index(&self) -> u64 { self.firmware.os.tick_index } + pub fn logical_frame_index(&self) -> u64 { self.firmware.os.logical_frame_index } + pub fn last_frame_cpu_time_us(&self) -> u64 { self.firmware.os.last_frame_cpu_time_us } + + /// "Contrato de frame" do PROMETEU. + pub fn step_frame(&mut self, signals: &InputSignals) { + // O Host chama o hardware, que delega para o Firmware/OS. + self.firmware.step_frame(signals); + } +} diff --git a/crates/core/src/lib.rs b/crates/core/src/lib.rs index 1522d9a6..cad9e75c 100644 --- a/crates/core/src/lib.rs +++ b/crates/core/src/lib.rs @@ -1,8 +1,9 @@ -pub mod logical_hardware; +pub mod hardware; pub mod peripherals; pub mod vm; mod model; -mod native_interface; -mod utilz; +pub mod firmware; +mod prometeu_os; +mod prometeu_hub; -pub use logical_hardware::LogicalHardware; +pub use hardware::Hardware; diff --git a/crates/core/src/logical_hardware.rs b/crates/core/src/logical_hardware.rs deleted file mode 100644 index 09a339a4..00000000 --- a/crates/core/src/logical_hardware.rs +++ /dev/null @@ -1,165 +0,0 @@ -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(); - } -} diff --git a/crates/core/src/model/cartridge.rs b/crates/core/src/model/cartridge.rs index bc02b3bf..b8d55ca7 100644 --- a/crates/core/src/model/cartridge.rs +++ b/crates/core/src/model/cartridge.rs @@ -1,12 +1,27 @@ use crate::vm::Program; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AppMode { + Game, + System, +} + +#[derive(Debug, Clone)] +pub struct AppHeader { + pub app_id: String, + pub title: String, + pub mode: AppMode, + pub entrypoint: u32, +} + #[derive(Clone, Debug)] pub struct Cartridge { + pub header: AppHeader, pub program: Program, } impl Cartridge { - pub fn new(program: Program) -> Self { - Self { program } + pub fn new(header: AppHeader, program: Program) -> Self { + Self { header, program } } } diff --git a/crates/core/src/model/mod.rs b/crates/core/src/model/mod.rs index 4d673091..ea406d98 100644 --- a/crates/core/src/model/mod.rs +++ b/crates/core/src/model/mod.rs @@ -14,4 +14,4 @@ pub use tile_bank::{TileBank, TileSize}; pub use tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap}; pub use sprite::Sprite; pub use sample::Sample; -pub use cartridge::Cartridge; +pub use cartridge::{Cartridge, AppMode}; diff --git a/crates/core/src/peripherals/mod.rs b/crates/core/src/peripherals/mod.rs index 70789ecf..bd102bb3 100644 --- a/crates/core/src/peripherals/mod.rs +++ b/crates/core/src/peripherals/mod.rs @@ -10,3 +10,19 @@ pub use input_signal::InputSignals; pub use pad::Pad; pub use touch::Touch; pub use audio::{Audio, Channel, AudioCommand, LoopMode, MAX_CHANNELS, OUTPUT_SAMPLE_RATE}; + +/// Interface de acesso ao hardware para o Firmware/OS. +/// Centraliza as chamadas para periféricos sem expor a estrutura interna do HardwareBridge. +pub trait HardwareBridge { + fn gfx(&self) -> &Gfx; + fn gfx_mut(&mut self) -> &mut Gfx; + + fn audio(&self) -> &Audio; + fn audio_mut(&mut self) -> &mut Audio; + + fn pad(&self) -> &Pad; + fn pad_mut(&mut self) -> &mut Pad; + + fn touch(&self) -> &Touch; + fn touch_mut(&mut self) -> &mut Touch; +} diff --git a/crates/core/src/prometeu_hub/mod.rs b/crates/core/src/prometeu_hub/mod.rs new file mode 100644 index 00000000..97e3702e --- /dev/null +++ b/crates/core/src/prometeu_hub/mod.rs @@ -0,0 +1,3 @@ +mod prometeu_hub; + +pub use prometeu_hub::PrometeuHub; \ No newline at end of file diff --git a/crates/core/src/prometeu_hub/prometeu_hub.rs b/crates/core/src/prometeu_hub/prometeu_hub.rs new file mode 100644 index 00000000..5ea2b5e6 --- /dev/null +++ b/crates/core/src/prometeu_hub/prometeu_hub.rs @@ -0,0 +1,40 @@ +use crate::model::Color; +use crate::prometeu_os::PrometeuOS; + +// TODO: Mover para arquivo próprio se crescer. +/// Sistema de janelas do PROMETEU. +pub struct WindowSystem { + pub theme_color: Color, +} + +impl WindowSystem { + pub fn new() -> Self { + Self { + theme_color: Color::INDIGO, + } + } +} + +/// PrometeuHub: Launcher e ambiente de UI do sistema. +pub struct PrometeuHub { + pub window_system: WindowSystem, +} + +impl PrometeuHub { + pub fn new() -> Self { + Self { + window_system: WindowSystem::new(), + } + } + + pub fn init(&mut self) { + // Inicializa o Window System e lista apps + } + + pub fn update(&mut self, os: &mut PrometeuOS) { + // Renderiza a UI básica do Hub + os.hardware_mut().gfx_mut().clear(self.window_system.theme_color); + // TODO: Desenhar lista de apps + os.hardware_mut().gfx_mut().present(); + } +} \ No newline at end of file diff --git a/crates/core/src/prometeu_os/mod.rs b/crates/core/src/prometeu_os/mod.rs new file mode 100644 index 00000000..d2c3ae9c --- /dev/null +++ b/crates/core/src/prometeu_os/mod.rs @@ -0,0 +1,5 @@ +mod prometeu_os; +mod native_interface; + +pub use prometeu_os::PrometeuOS; +pub use native_interface::NativeInterface; \ No newline at end of file diff --git a/crates/core/src/native_interface.rs b/crates/core/src/prometeu_os/native_interface.rs similarity index 75% rename from crates/core/src/native_interface.rs rename to crates/core/src/prometeu_os/native_interface.rs index 59a3bbde..f96ce8db 100644 --- a/crates/core/src/native_interface.rs +++ b/crates/core/src/prometeu_os/native_interface.rs @@ -1,28 +1,22 @@ +use crate::prometeu_os::prometeu_os::PrometeuOS; use crate::vm::{Value, VirtualMachine}; -use crate::LogicalHardware; pub trait NativeInterface { fn syscall(&mut self, id: u32, vm: &mut VirtualMachine) -> Result; } -impl NativeInterface for LogicalHardware { +impl NativeInterface for PrometeuOS { fn syscall(&mut self, id: u32, vm: &mut VirtualMachine) -> Result { match id { // system.has_cart() -> bool 0x0001 => { - vm.push(Value::Boolean(self.cartridge.is_some())); + vm.push(Value::Boolean(self.current_cartridge.is_some())); Ok(10) } // system.run_cart() 0x0002 => { - if let Some(cart) = self.cartridge.as_ref() { - vm.program = cart.program.clone(); - vm.pc = 0; - vm.operand_stack.clear(); - vm.call_stack.clear(); - vm.globals.clear(); - vm.heap.clear(); - vm.halted = false; + if let Some(cart) = self.current_cartridge.as_ref().cloned() { + self.load_cartridge(&cart); } else { return Err("No cartridge inserted".into()); } @@ -32,7 +26,7 @@ impl NativeInterface for LogicalHardware { 0x1001 => { let color_idx = vm.pop_integer()? as usize; let color = self.get_color(color_idx); - self.gfx.clear(color); + self.hardware_mut().gfx_mut().clear(color); Ok(100) } // gfx.draw_rect(x, y, w, h, color_index) @@ -43,7 +37,7 @@ impl NativeInterface for LogicalHardware { let y = vm.pop_integer()? as i32; let x = vm.pop_integer()? as i32; let color = self.get_color(color_idx); - self.gfx.fill_rect(x, y, w, h, color); + self.hardware_mut().gfx_mut().fill_rect(x, y, w, h, color); Ok(200) } // input.get_pad(button_id) -> bool @@ -54,7 +48,6 @@ impl NativeInterface for LogicalHardware { Ok(50) } // audio.play_sample(sample_id, voice_id, volume, pan, pitch) - // sample_id: 0=square, 1=kick, 2=snare 0x3001 => { let pitch = vm.pop_number()?; let pan = vm.pop_integer()? as u8; @@ -70,7 +63,7 @@ impl NativeInterface for LogicalHardware { }; if let Some(s) = sample { - self.audio.play(s, voice_id, volume, pan, pitch, 0, crate::peripherals::LoopMode::Off); + self.hardware_mut().audio_mut().play(s, voice_id, volume, pan, pitch, 0, crate::peripherals::LoopMode::Off); } Ok(300) } diff --git a/crates/core/src/prometeu_os/prometeu_os.rs b/crates/core/src/prometeu_os/prometeu_os.rs new file mode 100644 index 00000000..d5bfba8c --- /dev/null +++ b/crates/core/src/prometeu_os/prometeu_os.rs @@ -0,0 +1,182 @@ +use crate::model::{Cartridge, Color, Sample}; +use crate::peripherals::{HardwareBridge, InputSignals}; +use crate::vm::VirtualMachine; +use std::sync::Arc; + +/// PrometeuOS (POS): O firmware/base do sistema. +/// Autoridade máxima do boot, periféricos, execução da PVM e tratamento de falhas. +pub struct PrometeuOS { + pub vm: VirtualMachine, + pub tick_index: u64, + pub logical_frame_index: u64, + pub logical_frame_open: bool, + pub last_frame_cpu_time_us: u64, + pub hw_ptr: Option<*mut dyn HardwareBridge>, + pub current_cartridge: Option, + + // Assets de exemplo (mantidos para compatibilidade com syscalls de áudio v0) + pub sample_square: Option>, + pub sample_kick: Option>, + pub sample_snare: Option>, +} + +impl PrometeuOS { + pub const CYCLES_PER_FRAME: u64 = 100_000; + + pub fn new() -> Self { + let mut os = Self { + vm: VirtualMachine::default(), + hw_ptr: None, + tick_index: 0, + logical_frame_index: 0, + logical_frame_open: false, + last_frame_cpu_time_us: 0, + current_cartridge: None, + sample_square: None, + sample_kick: None, + sample_snare: None, + }; + + // Inicializa samples básicos (mesma lógica do LogicalHardware) + os.sample_square = Some(Arc::new(Self::create_square_sample(440.0, 0.1))); + + os + } + + /// Helper para acessar o hardware de forma segura (dentro do contexto do firmware). + #[inline] + pub fn hardware(&self) -> &dyn HardwareBridge { + unsafe { &*self.hw_ptr.expect("Hardware pointer not initialized") } + } + + /// Helper para acessar o hardware de forma mutável. + #[inline] + pub fn hardware_mut(&mut self) -> &mut dyn HardwareBridge { + unsafe { &mut *self.hw_ptr.expect("Hardware pointer not initialized") } + } + + pub fn reset(&mut self) { + self.vm = VirtualMachine::default(); + self.tick_index = 0; + self.logical_frame_index = 0; + self.logical_frame_open = false; + self.last_frame_cpu_time_us = 0; + self.current_cartridge = None; + } + + /// Carrega um cartucho na PVM e reseta o estado de execução. + pub fn load_cartridge(&mut self, cart: &Cartridge) { + // Na spec: "resetar PC/stack/heap ao iniciar app" + self.current_cartridge = Some(cart.clone()); + self.vm.program = cart.program.clone(); + self.vm.pc = 0; + self.vm.operand_stack.clear(); + self.vm.call_stack.clear(); + self.vm.globals.clear(); + self.vm.heap.clear(); + self.vm.halted = false; + } + + /// Executa um tick do host (60Hz). + pub fn step_frame(&mut self, signals: &InputSignals) -> Option { + let start = std::time::Instant::now(); + self.tick_index += 1; + + if !self.logical_frame_open { + self.logical_frame_open = true; + self.begin_logical_frame(signals); + } + + // Executa budget + let mut vm = std::mem::take(&mut self.vm); + let run_result = vm.run_budget(Self::CYCLES_PER_FRAME, self); + self.vm = vm; + + match run_result { + Ok(run) => { + if run.reason == crate::vm::LogicalFrameEndingReason::FrameSync { + self.hardware_mut().gfx_mut().render_all(); + self.end_logical_frame(); + self.logical_frame_index += 1; + self.logical_frame_open = false; + } + } + Err(e) => { + return Some(format!("PVM Fault: {:?}", e)); + } + } + + self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64; + None + } + + fn begin_logical_frame(&mut self, signals: &InputSignals) { + let hw = self.hardware_mut(); + hw.audio_mut().clear_commands(); + hw.touch_mut().begin_frame(signals); + hw.pad_mut().begin_frame(signals); + } + + fn end_logical_frame(&mut self) { + self.hardware_mut().gfx_mut().present(); + } + + // Helper para syscalls + pub fn get_color(&self, index: usize) -> Color { + let hw = self.hardware(); + if let Some(bank) = hw.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, + } + } + } + + // Helper para syscalls + pub fn is_button_down(&self, id: u32) -> bool { + let hw = self.hardware(); + match id { + 0 => hw.pad().up.down, + 1 => hw.pad().down.down, + 2 => hw.pad().left.down, + 3 => hw.pad().right.down, + 4 => hw.pad().a.down, + 5 => hw.pad().b.down, + 6 => hw.pad().x.down, + 7 => hw.pad().y.down, + 8 => hw.pad().l.down, + 9 => hw.pad().r.down, + 10 => hw.pad().start.down, + 11 => hw.pad().select.down, + _ => false, + } + } + + 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 + } else { + -10000 + }; + data.push(val); + } + + Sample::new(sample_rate, data) + } +} \ No newline at end of file diff --git a/crates/core/src/utilz.rs b/crates/core/src/utilz.rs deleted file mode 100644 index 21804c03..00000000 --- a/crates/core/src/utilz.rs +++ /dev/null @@ -1,18 +0,0 @@ -use crate::vm::OpCode; - -pub fn emit_u16(rom: &mut Vec, val: u16) { - rom.extend_from_slice(&val.to_le_bytes()); -} - -pub fn emit_u32(rom: &mut Vec, val: u32) { - rom.extend_from_slice(&val.to_le_bytes()); -} - -pub fn emit_op(rom: &mut Vec, op: OpCode) { - emit_u16(rom, op as u16); -} - -pub fn patch_u32(rom: &mut [u8], offset: usize, val: u32) { - let bytes = val.to_le_bytes(); - rom[offset..offset + 4].copy_from_slice(&bytes); -} diff --git a/crates/core/src/vm/virtual_machine.rs b/crates/core/src/vm/virtual_machine.rs index bdb18435..9197d1c4 100644 --- a/crates/core/src/vm/virtual_machine.rs +++ b/crates/core/src/vm/virtual_machine.rs @@ -1,9 +1,7 @@ -use crate::native_interface::NativeInterface; -use crate::utilz; +use crate::prometeu_os::NativeInterface; use crate::vm::call_frame::CallFrame; use crate::vm::opcode::OpCode; use crate::vm::value::Value; - use crate::vm::Program; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -44,246 +42,11 @@ impl VirtualMachine { halted: false, } } - - /// Cria uma VM com um "Boot ROM" básico que exibe uma animação de padrão de teste. - /// Isso garante que a máquina sempre mostre algo ao ser ligada, mesmo sem cartucho. - pub fn new_boot_rom() -> Self { - let mut rom = Vec::new(); - let mut constant_pool = Vec::new(); - - // Constant Pool - constant_pool.push(Value::Integer(160)); // 0: Center X - constant_pool.push(Value::Integer(90)); // 1: Center Y - constant_pool.push(Value::Integer(40)); // 2: Logo Size - constant_pool.push(Value::Integer(7)); // 3: Indigo (Logo) - constant_pool.push(Value::Integer(0)); // 4: Black (Background) - constant_pool.push(Value::Integer(180)); // 5: Splash Timeout (3s) - constant_pool.push(Value::Integer(2)); // 6: Divisor - constant_pool.push(Value::Integer(1)); // 7: 1 - constant_pool.push(Value::Integer(10)); // 8: Start Button - constant_pool.push(Value::Integer(0)); // 9: Square Sample - constant_pool.push(Value::Integer(255)); // 10: Volume - constant_pool.push(Value::Integer(127)); // 11: Pan - constant_pool.push(Value::Float(1.0)); // 12: Pitch - constant_pool.push(Value::Integer(0)); // 13: Voice ID - constant_pool.push(Value::Integer(0)); // 14: Zero - constant_pool.push(Value::Integer(6)); // 15: Cyan (SO Logo) - - // -- INICIALIZAÇÃO -- - - // G0 = 0 (Contador de frames) - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 14); - utilz::emit_op(&mut rom, OpCode::SetGlobal); - utilz::emit_u32(&mut rom, 0); - - // G1 = 0 (Estado BIOS: 0=Splash, 1=SO) - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 14); - utilz::emit_op(&mut rom, OpCode::SetGlobal); - utilz::emit_u32(&mut rom, 1); - - // Toca o som de boot "plim" - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 9); // sample - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 13); // voice - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 10); // vol - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 11); // pan - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 12); // pitch - utilz::emit_op(&mut rom, OpCode::Syscall); - utilz::emit_u32(&mut rom, 0x3001); - - // Escopo local para cálculos temporários [0:f, 1:s, 2:x, 3:y] - utilz::emit_op(&mut rom, OpCode::PushScope); - utilz::emit_u32(&mut rom, 4); - - let loop_start = rom.len() as u32; - - // Limpa tela - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 4); // black - utilz::emit_op(&mut rom, OpCode::Syscall); - utilz::emit_u32(&mut rom, 0x1001); - - // --- MÁQUINA DE ESTADOS (G1) --- - utilz::emit_op(&mut rom, OpCode::GetGlobal); - utilz::emit_u32(&mut rom, 1); // G1 - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 14); // 0 - utilz::emit_op(&mut rom, OpCode::Eq); - let jmp_to_so = rom.len(); - utilz::emit_op(&mut rom, OpCode::JmpIfFalse); - utilz::emit_u32(&mut rom, 0); - - // === ESTADO 0: SPLASH === - - // 1. Cálculos da Animação - utilz::emit_op(&mut rom, OpCode::GetGlobal); - utilz::emit_u32(&mut rom, 0); - utilz::emit_op(&mut rom, OpCode::SetLocal); - utilz::emit_u32(&mut rom, 0); // L0 = f - - // S = if f < 40 { f } else { 40 } - utilz::emit_op(&mut rom, OpCode::GetLocal); - utilz::emit_u32(&mut rom, 0); // f - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 2); // 40 - utilz::emit_op(&mut rom, OpCode::Lt); - let jmp_s_full = rom.len(); - utilz::emit_op(&mut rom, OpCode::JmpIfFalse); - utilz::emit_u32(&mut rom, 0); - - utilz::emit_op(&mut rom, OpCode::GetLocal); - utilz::emit_u32(&mut rom, 0); // f - let jmp_s_set = rom.len(); - utilz::emit_op(&mut rom, OpCode::Jmp); - utilz::emit_u32(&mut rom, 0); - - let s_full_addr = rom.len() as u32; - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 2); // 40 - - let s_set_addr = rom.len() as u32; - utilz::patch_u32(&mut rom, jmp_s_full + 2, s_full_addr); - utilz::patch_u32(&mut rom, jmp_s_set + 2, s_set_addr); - utilz::emit_op(&mut rom, OpCode::SetLocal); - utilz::emit_u32(&mut rom, 1); // L1 = S - - // X = 160 - S/2, Y = 90 - S/2 - for (local_idx, const_idx) in [(2, 0), (3, 1)] { - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, const_idx); - utilz::emit_op(&mut rom, OpCode::GetLocal); - utilz::emit_u32(&mut rom, 1); // S - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 6); // 2 - utilz::emit_op(&mut rom, OpCode::Div); - utilz::emit_op(&mut rom, OpCode::Sub); - utilz::emit_op(&mut rom, OpCode::SetLocal); - utilz::emit_u32(&mut rom, local_idx); - } - - // Desenha Quadrado (Logo) - utilz::emit_op(&mut rom, OpCode::GetLocal); - utilz::emit_u32(&mut rom, 2); // X - utilz::emit_op(&mut rom, OpCode::GetLocal); - utilz::emit_u32(&mut rom, 3); // Y - utilz::emit_op(&mut rom, OpCode::GetLocal); - utilz::emit_u32(&mut rom, 1); // W - utilz::emit_op(&mut rom, OpCode::GetLocal); - utilz::emit_u32(&mut rom, 1); // H - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 3); // Indigo - utilz::emit_op(&mut rom, OpCode::Syscall); - utilz::emit_u32(&mut rom, 0x1002); - - // Checagem de Saída do Splash (f >= 180 OR START) - utilz::emit_op(&mut rom, OpCode::GetGlobal); - utilz::emit_u32(&mut rom, 0); // f - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 5); // 180 - utilz::emit_op(&mut rom, OpCode::Gt); // f > 180 (ou eq, mas gt é mais seguro) - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 8); // Start - utilz::emit_op(&mut rom, OpCode::Syscall); - utilz::emit_u32(&mut rom, 0x2001); // get_pad - utilz::emit_op(&mut rom, OpCode::Or); - let jmp_splash_continue = rom.len(); - utilz::emit_op(&mut rom, OpCode::JmpIfFalse); - utilz::emit_u32(&mut rom, 0); - - // Validação de Cartucho - utilz::emit_op(&mut rom, OpCode::Syscall); - utilz::emit_u32(&mut rom, 0x0001); // has_cart? - let jmp_no_cart_init = rom.len(); - utilz::emit_op(&mut rom, OpCode::JmpIfFalse); - utilz::emit_u32(&mut rom, 0); - - utilz::emit_op(&mut rom, OpCode::Syscall); - utilz::emit_u32(&mut rom, 0x0002); // run_cart - - // Senão tem cartucho: vai para o SO - let no_cart_init_addr = rom.len() as u32; - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 7); // 1 - utilz::emit_op(&mut rom, OpCode::SetGlobal); - utilz::emit_u32(&mut rom, 1); // G1 = 1 (SO) - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 14); // 0 - utilz::emit_op(&mut rom, OpCode::SetGlobal); - utilz::emit_u32(&mut rom, 0); // f = 0 (reset counter for SO) - - let splash_continue_addr = rom.len() as u32; - utilz::patch_u32(&mut rom, jmp_splash_continue + 2, splash_continue_addr); - utilz::patch_u32(&mut rom, jmp_no_cart_init + 2, no_cart_init_addr); - - let jmp_end_state = rom.len(); - utilz::emit_op(&mut rom, OpCode::Jmp); - utilz::emit_u32(&mut rom, 0); - - // === ESTADO 1: SO === - let so_state_addr = rom.len() as u32; - utilz::patch_u32(&mut rom, jmp_to_so + 2, so_state_addr); - - // Desenha "SO" (Quadrado Ciano) - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 0); // 160 - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 1); // 90 - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 2); // 40 - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 2); // 40 - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 15); // Cyan - utilz::emit_op(&mut rom, OpCode::Syscall); - utilz::emit_u32(&mut rom, 0x1002); - - // Monitora Inserção de Cartucho (Hot-plug) + START - utilz::emit_op(&mut rom, OpCode::Syscall); - utilz::emit_u32(&mut rom, 0x0001); // has_cart? - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 8); // Start - utilz::emit_op(&mut rom, OpCode::Syscall); - utilz::emit_u32(&mut rom, 0x2001); // get_pad - utilz::emit_op(&mut rom, OpCode::And); // has_cart AND START - let jmp_so_continue = rom.len(); - utilz::emit_op(&mut rom, OpCode::JmpIfFalse); - utilz::emit_u32(&mut rom, 0); - - utilz::emit_op(&mut rom, OpCode::Syscall); - utilz::emit_u32(&mut rom, 0x0002); // run_cart - - let so_continue_addr = rom.len() as u32; - utilz::patch_u32(&mut rom, jmp_so_continue + 2, so_continue_addr); - - // Incrementa G0 (f) - let end_state_addr = rom.len() as u32; - utilz::patch_u32(&mut rom, jmp_end_state + 2, end_state_addr); - - utilz::emit_op(&mut rom, OpCode::GetGlobal); - utilz::emit_u32(&mut rom, 0); - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 7); // 1 - utilz::emit_op(&mut rom, OpCode::Add); - utilz::emit_op(&mut rom, OpCode::SetGlobal); - utilz::emit_u32(&mut rom, 0); - - utilz::emit_op(&mut rom, OpCode::FrameSync); - utilz::emit_op(&mut rom, OpCode::Jmp); - utilz::emit_u32(&mut rom, loop_start); - - Self::new(rom, constant_pool) - } } impl Default for VirtualMachine { fn default() -> Self { - Self::new_boot_rom() + Self::new(vec![], vec![]) } } @@ -633,255 +396,3 @@ impl VirtualMachine { Ok(()) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_add_instructions() { - struct NoopNative; - impl NativeInterface for NoopNative { - fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result { Ok(0) } - } - let mut native = NoopNative; - - let mut rom = Vec::new(); - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 0); // Const index 0 - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 1); // Const index 1 - utilz::emit_op(&mut rom, OpCode::Add); - utilz::emit_op(&mut rom, OpCode::Halt); - - let constant_pool = vec![Value::Integer(10), Value::Integer(20)]; - let mut vm = VirtualMachine::new(rom, constant_pool); - - vm.run_budget(100, &mut native).unwrap(); - - assert_eq!(vm.operand_stack.len(), 1); - assert_eq!(vm.operand_stack[0], Value::Integer(30)); - assert!(vm.halted); - } - - #[test] - fn test_jump_and_loop() { - struct NoopNative; - impl NativeInterface for NoopNative { - fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result { Ok(0) } - } - let mut native = NoopNative; - - let mut rom = Vec::new(); - // Index 0: PUSH 0 (counter) - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 0); - - // Index 6: DUP - let loop_start = rom.len(); - utilz::emit_op(&mut rom, OpCode::Dup); - - // PUSH 10 - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 1); - - // LT (counter < 10) - utilz::emit_op(&mut rom, OpCode::Lt); - - // JMP_IF_FALSE to end - utilz::emit_op(&mut rom, OpCode::JmpIfFalse); - let jmp_placeholder = rom.len(); - utilz::emit_u32(&mut rom, 0); - - // PUSH 1 - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 2); - - // ADD (increment counter) - utilz::emit_op(&mut rom, OpCode::Add); - - // JMP to start - utilz::emit_op(&mut rom, OpCode::Jmp); - utilz::emit_u32(&mut rom, loop_start as u32); - - // End - let loop_end = rom.len(); - utilz::emit_op(&mut rom, OpCode::Halt); - - // Patch JMP_IF_FALSE addr - let end_addr_bytes = (loop_end as u32).to_le_bytes(); - rom[jmp_placeholder..jmp_placeholder+4].copy_from_slice(&end_addr_bytes); - - let constant_pool = vec![Value::Integer(0), Value::Integer(10), Value::Integer(1)]; - let mut vm = VirtualMachine::new(rom, constant_pool); - - vm.run_budget(1000, &mut native).unwrap(); - - assert!(vm.halted); - // O valor final na pilha deve ser 10 - assert_eq!(vm.operand_stack.last(), Some(&Value::Integer(10))); - } - - #[test] - fn test_native_call_gfx_clear() { - struct MockGfx { - cleared_color: Option, - } - impl NativeInterface for MockGfx { - fn syscall(&mut self, id: u32, vm: &mut VirtualMachine) -> Result { - if id == 0x1001 { - let color = vm.pop_integer()? as usize; - self.cleared_color = Some(color); - return Ok(100); - } - Ok(0) - } - } - let mut native = MockGfx { cleared_color: None }; - - let mut rom = Vec::new(); - // PUSH 5 (color index) - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 0); - // CALL_NATIVE 0x1001 - utilz::emit_op(&mut rom, OpCode::Syscall); - utilz::emit_u32(&mut rom, 0x1001); - utilz::emit_op(&mut rom, OpCode::Halt); - - let constant_pool = vec![Value::Integer(5)]; - let mut vm = VirtualMachine::new(rom, constant_pool); - - vm.run_budget(1000, &mut native).unwrap(); - - assert!(vm.halted); - assert_eq!(native.cleared_color, Some(5)); - } - - #[test] - fn test_system_run_cart() { - use crate::LogicalHardware; - use crate::model::Cartridge; - - let mut logical_hardware = LogicalHardware::new(); - - // 1. Verifica que não tem cartucho inicialmente - let mut rom = Vec::new(); - // CALL_NATIVE 0x0001 (has_cart) - utilz::emit_op(&mut rom, OpCode::Syscall); - utilz::emit_u32(&mut rom, 0x0001); - utilz::emit_op(&mut rom, OpCode::Halt); - - logical_hardware.vm = VirtualMachine::new(rom, vec![]); - let mut vm = std::mem::take(&mut logical_hardware.vm); - vm.run_budget(100, &mut logical_hardware).unwrap(); - logical_hardware.vm = vm; - - assert_eq!(logical_hardware.vm.pop().unwrap(), Value::Boolean(false)); - - // 2. Adiciona um cartucho e roda - let mut cart_rom = Vec::new(); - // PUSH_CONST 0 - utilz::emit_op(&mut cart_rom, OpCode::PushConst); - utilz::emit_u32(&mut cart_rom, 0); - utilz::emit_op(&mut cart_rom, OpCode::Halt); - let cart_pool = vec![Value::Integer(42)]; - - let cart = Cartridge::new(Program::new(cart_rom, cart_pool)); - logical_hardware.load_cartridge(cart); - - // Código para rodar o cartucho - let mut boot_rom = Vec::new(); - // CALL_NATIVE 0x0002 (run_cart) - utilz::emit_op(&mut boot_rom, OpCode::Syscall); - utilz::emit_u32(&mut boot_rom, 0x0002); - - logical_hardware.vm = VirtualMachine::new(boot_rom, vec![]); - let mut vm = std::mem::take(&mut logical_hardware.vm); - vm.run_budget(1000, &mut logical_hardware).unwrap(); - logical_hardware.vm = vm; - - // Após o run_budget, a VM deve ter executado o cartucho - assert_eq!(logical_hardware.vm.operand_stack.last(), Some(&Value::Integer(42))); - assert!(logical_hardware.vm.halted); - } - - #[test] - fn test_stack_operations() { - struct NoopNative; - impl NativeInterface for NoopNative { - fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result { Ok(0) } - } - let mut native = NoopNative; - - let mut rom = Vec::new(); - // PUSH 10, PUSH 20, SWAP, POP - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 0); - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 1); - utilz::emit_op(&mut rom, OpCode::Swap); - utilz::emit_op(&mut rom, OpCode::Pop); - utilz::emit_op(&mut rom, OpCode::Halt); - - let constant_pool = vec![Value::Integer(10), Value::Integer(20)]; - let mut vm = VirtualMachine::new(rom, constant_pool); - vm.run_budget(100, &mut native).unwrap(); - - assert_eq!(vm.operand_stack.len(), 1); - assert_eq!(vm.operand_stack[0], Value::Integer(20)); - } - - #[test] - fn test_logical_operations() { - struct NoopNative; - impl NativeInterface for NoopNative { - fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result { Ok(0) } - } - let mut native = NoopNative; - - let mut rom = Vec::new(); - // PUSH true, NOT (-> false), PUSH true, OR (-> true) - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 0); // true - utilz::emit_op(&mut rom, OpCode::Not); - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 0); // true - utilz::emit_op(&mut rom, OpCode::Or); - utilz::emit_op(&mut rom, OpCode::Halt); - - let constant_pool = vec![Value::Boolean(true)]; - let mut vm = VirtualMachine::new(rom, constant_pool); - vm.run_budget(100, &mut native).unwrap(); - - assert_eq!(vm.operand_stack.last(), Some(&Value::Boolean(true))); - } - - #[test] - fn test_scopes() { - struct NoopNative; - impl NativeInterface for NoopNative { - fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result { Ok(0) } - } - let mut native = NoopNative; - - let mut rom = Vec::new(); - // PUSH_SCOPE 2 (reserves 2 nulls), PUSH 42, SET_LOCAL 0, GET_LOCAL 0, POP_SCOPE - utilz::emit_op(&mut rom, OpCode::PushScope); - utilz::emit_u32(&mut rom, 2); - utilz::emit_op(&mut rom, OpCode::PushConst); - utilz::emit_u32(&mut rom, 0); // 42 - utilz::emit_op(&mut rom, OpCode::SetLocal); - utilz::emit_u32(&mut rom, 0); - utilz::emit_op(&mut rom, OpCode::GetLocal); - utilz::emit_u32(&mut rom, 0); - utilz::emit_op(&mut rom, OpCode::PopScope); - utilz::emit_op(&mut rom, OpCode::Halt); - - let constant_pool = vec![Value::Integer(42)]; - let mut vm = VirtualMachine::new(rom, constant_pool); - vm.run_budget(100, &mut native).unwrap(); - - // Stack should be empty because POP_SCOPE truncates to stack_base - assert_eq!(vm.operand_stack.len(), 0); - } -} diff --git a/crates/host_desktop/src/prometeu_runner.rs b/crates/host_desktop/src/prometeu_runner.rs index 865ffe52..591a8df3 100644 --- a/crates/host_desktop/src/prometeu_runner.rs +++ b/crates/host_desktop/src/prometeu_runner.rs @@ -2,7 +2,7 @@ use crate::audio_mixer::AudioMixer; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use pixels::{Pixels, SurfaceTexture}; use prometeu_core::peripherals::{AudioCommand, InputSignals, OUTPUT_SAMPLE_RATE}; -use prometeu_core::LogicalHardware; +use prometeu_core::Hardware; use ringbuf::traits::{Consumer, Producer, Split}; use ringbuf::HeapRb; use std::sync::Arc; @@ -18,7 +18,7 @@ pub struct PrometeuRunner { window: Option<&'static Window>, pixels: Option>, - logical_hardware: LogicalHardware, + hardware_bridge: Box, input_signals: InputSignals, @@ -43,11 +43,8 @@ impl PrometeuRunner { Self { window: None, pixels: None, - - logical_hardware: LogicalHardware::new(), - + hardware_bridge: Hardware::new(), input_signals: InputSignals::default(), - frame_target_dt: Duration::from_nanos(1_000_000_000 / target_fps), last_frame_time: Instant::now(), accumulator: Duration::ZERO, @@ -148,7 +145,7 @@ impl ApplicationHandler for PrometeuRunner { let size = window.inner_size(); let surface_texture = SurfaceTexture::new(size.width, size.height, window); - let mut pixels = Pixels::new(LogicalHardware::W as u32, LogicalHardware::H as u32, surface_texture) + 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); @@ -183,7 +180,7 @@ impl ApplicationHandler for PrometeuRunner { let frame = pixels.frame_mut(); // Borrow imutável do core (campo diferente, ok) - let src = self.logical_hardware.gfx.front_buffer(); + let src = self.hardware_bridge.gfx.front_buffer(); draw_rgb565_to_rgba8(src, frame); } // <- frame borrow termina aqui @@ -256,11 +253,11 @@ impl ApplicationHandler for PrometeuRunner { // 🔥 O coração do determinismo: consome o tempo em fatias exatas de 60Hz while self.accumulator >= self.frame_target_dt { - self.logical_hardware.step_frame(&self.input_signals); + self.hardware_bridge.step_frame(&self.input_signals); // Envia comandos de áudio gerados neste frame para a thread de áudio if let Some(producer) = &mut self.audio_producer { - for cmd in self.logical_hardware.audio.commands.drain(..) { + for cmd in self.hardware_bridge.audio.commands.drain(..) { let _ = producer.try_push(cmd); } } @@ -282,11 +279,11 @@ impl ApplicationHandler for PrometeuRunner { if stats_elapsed >= Duration::from_secs(1) { if let Some(window) = self.window { let fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64(); - let kb = self.logical_hardware.gfx.memory_usage_bytes() as f64 / 1024.0; + let kb = self.hardware_bridge.gfx.memory_usage_bytes() as f64 / 1024.0; // comparação fixa sempre contra 60Hz, manter mesmo quando fazer teste de stress na CPU let frame_budget_us = 16666.0; - let cpu_load_core = (self.logical_hardware.last_frame_cpu_time_us as f64 / frame_budget_us) * 100.0; + let cpu_load_core = (self.hardware_bridge.last_frame_cpu_time_us() as f64 / frame_budget_us) * 100.0; let cpu_load_audio = if self.audio_load_samples > 0 { // O load real é (tempo total processando) / (tempo total de parede). @@ -297,7 +294,7 @@ impl ApplicationHandler for PrometeuRunner { let title = format!( "PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Load: {:.1}% (C) + {:.1}% (A) | Frame: tick {} logical {}", - kb, fps, cpu_load_core, cpu_load_audio, self.logical_hardware.tick_index, self.logical_hardware.logical_frame_index + kb, fps, cpu_load_core, cpu_load_audio, self.hardware_bridge.tick_index(), self.hardware_bridge.logical_frame_index() ); window.set_title(&title); } @@ -316,13 +313,13 @@ impl ApplicationHandler for PrometeuRunner { /// Depois podemos fazer letterbox/aspect-ratio correto. fn window_to_fb(wx: f32, wy: f32, window: &Window) -> (i32, i32) { let size = window.inner_size(); - let fb_w = LogicalHardware::W as f32; - let fb_h = LogicalHardware::H as f32; + let fb_w = Hardware::W as f32; + let fb_h = Hardware::H as f32; let x = (wx * fb_w / size.width as f32).floor() as i32; let y = (wy * fb_h / size.height as f32).floor() as i32; - (x.clamp(0, LogicalHardware::W as i32 - 1), y.clamp(0, LogicalHardware::H as i32 - 1)) + (x.clamp(0, Hardware::W as i32 - 1), y.clamp(0, Hardware::H as i32 - 1)) } /// Copia RGB565 (u16) -> RGBA8888 (u8[4]) para o frame do pixels.