diff --git a/crates/core/src/logical_hardware.rs b/crates/core/src/logical_hardware.rs index 30f7162a..3f3ef2c1 100644 --- a/crates/core/src/logical_hardware.rs +++ b/crates/core/src/logical_hardware.rs @@ -15,14 +15,15 @@ pub struct LogicalHardware { 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; @@ -38,6 +39,7 @@ impl LogicalHardware { last_frame_cpu_time_us: 0, vm: VirtualMachine::default(), cartridge: None, + logical_frame_open: false, sample_square: None, sample_kick: None, sample_snare: None, @@ -45,7 +47,7 @@ impl LogicalHardware { // Inicializa samples básicos logical_hardware.sample_square = Some(Arc::new(Self::create_square_sample(440.0, 0.1))); - + logical_hardware } @@ -114,24 +116,34 @@ impl LogicalHardware { /// 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.begin_frame(signals); - - // Retira a VM temporariamente para executar run_budget passando self (Logical Hardware) como NativeInterface. - // Como VirtualMachine implementa Default, usamos std::mem::take. + + // 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 _result = vm.run_budget(Self::CYCLES_PER_FRAME, self); + let run = vm + .run_budget(Self::CYCLES_PER_FRAME, self) + .expect("vm error"); self.vm = vm; - self.gfx.render_all(); // A máquina executa o pipeline gráfico (Ação física) - self.end_frame(); // present/housekeeping - self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64; // Calcula quanto tempo a "CPU simulada" trabalhou de verdade + // 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.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) { - self.frame_index += 1; - - // Limpa comandos de áudio do frame anterior. + // 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(); diff --git a/crates/core/src/vm/mod.rs b/crates/core/src/vm/mod.rs index bc214d4f..a1640c03 100644 --- a/crates/core/src/vm/mod.rs +++ b/crates/core/src/vm/mod.rs @@ -4,7 +4,8 @@ mod opcode; mod call_frame; mod program; -pub use value::Value; -pub use virtual_machine::VirtualMachine; pub use opcode::OpCode; pub use program::Program; +pub use value::Value; +pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine}; + diff --git a/crates/core/src/vm/virtual_machine.rs b/crates/core/src/vm/virtual_machine.rs index 459d0572..bdb18435 100644 --- a/crates/core/src/vm/virtual_machine.rs +++ b/crates/core/src/vm/virtual_machine.rs @@ -4,9 +4,22 @@ 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)] +pub enum LogicalFrameEndingReason { + FrameSync, + BudgetExhausted, + Halted, + EndOfRom, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct BudgetReport { + pub cycles_used: u64, + pub reason: LogicalFrameEndingReason, +} + pub struct VirtualMachine { pub pc: usize, pub operand_stack: Vec, @@ -275,33 +288,53 @@ impl Default for VirtualMachine { } impl VirtualMachine { - pub fn run_budget(&mut self, budget: u64, native: &mut dyn NativeInterface) -> Result { + pub fn run_budget( + &mut self, + budget: u64, + native: &mut dyn NativeInterface + ) -> Result { let start_cycles = self.cycles; - let mut budget_used = 0; + let mut ending_reason: Option = None; - while budget_used < budget && !self.halted && self.pc < self.program.rom.len() { + while (self.cycles - start_cycles) < budget + && !self.halted + && self.pc < self.program.rom.len() + { let pc_before = self.pc; let cycles_before = self.cycles; + + // Fast-path: FRAME_SYNC encerra o frame lógico let opcode_val = self.peek_u16()?; let opcode = OpCode::try_from(opcode_val)?; - if opcode == OpCode::FrameSync { self.pc += 2; self.cycles += OpCode::FrameSync.cycles(); + ending_reason = Some(LogicalFrameEndingReason::FrameSync); break; } self.step(native)?; - - // Garantir progresso para evitar loop infinito real (onde nem PC nem ciclos avançam) + + // garante progresso real if self.pc == pc_before && self.cycles == cycles_before && !self.halted { return Err(format!("VM stuck at PC 0x{:08X}", self.pc)); } - - budget_used = self.cycles - start_cycles; } - Ok(budget_used) + if ending_reason.is_none() { + if self.halted { + ending_reason = Some(LogicalFrameEndingReason::Halted); + } else if self.pc >= self.program.rom.len() { + ending_reason = Some(LogicalFrameEndingReason::EndOfRom); + } else { + ending_reason = Some(LogicalFrameEndingReason::BudgetExhausted); + } + } + + Ok(BudgetReport { + cycles_used: self.cycles - start_cycles, + reason: ending_reason.unwrap(), + }) } fn peek_u16(&self) -> Result {