logical frame computed by cost

This commit is contained in:
bQUARKz 2026-01-16 06:11:25 +00:00
parent 85a5f2d63f
commit 43e366bcd5
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
3 changed files with 72 additions and 26 deletions

View File

@ -15,14 +15,15 @@ pub struct LogicalHardware {
pub vm: VirtualMachine, pub vm: VirtualMachine,
pub cartridge: Option<Cartridge>, pub cartridge: Option<Cartridge>,
// Estado Interno
logical_frame_open: bool,
// Assets de exemplo // Assets de exemplo
pub sample_square: Option<Arc<Sample>>, pub sample_square: Option<Arc<Sample>>,
pub sample_kick: Option<Arc<Sample>>, pub sample_kick: Option<Arc<Sample>>,
pub sample_snare: Option<Arc<Sample>>, pub sample_snare: Option<Arc<Sample>>,
} }
impl LogicalHardware { impl LogicalHardware {
pub const W: usize = 320; pub const W: usize = 320;
pub const H: usize = 180; pub const H: usize = 180;
@ -38,6 +39,7 @@ impl LogicalHardware {
last_frame_cpu_time_us: 0, last_frame_cpu_time_us: 0,
vm: VirtualMachine::default(), vm: VirtualMachine::default(),
cartridge: None, cartridge: None,
logical_frame_open: false,
sample_square: None, sample_square: None,
sample_kick: None, sample_kick: None,
sample_snare: None, sample_snare: None,
@ -45,7 +47,7 @@ impl LogicalHardware {
// Inicializa samples básicos // Inicializa samples básicos
logical_hardware.sample_square = Some(Arc::new(Self::create_square_sample(440.0, 0.1))); logical_hardware.sample_square = Some(Arc::new(Self::create_square_sample(440.0, 0.1)));
logical_hardware logical_hardware
} }
@ -114,24 +116,34 @@ impl LogicalHardware {
/// O Host controla tempo/event loop; o Core define a sequência do frame. /// O Host controla tempo/event loop; o Core define a sequência do frame.
pub fn step_frame(&mut self, signals: &InputSignals) { pub fn step_frame(&mut self, signals: &InputSignals) {
let start = std::time::Instant::now(); let start = std::time::Instant::now();
self.begin_frame(signals);
// Se um frame logica estiver aberto evita limpar o input
// Retira a VM temporariamente para executar run_budget passando self (Logical Hardware) como NativeInterface. if !self.logical_frame_open {
// Como VirtualMachine implementa Default, usamos std::mem::take. 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 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.vm = vm;
self.gfx.render_all(); // A máquina executa o pipeline gráfico (Ação física) // Só “materializa” e apresenta quando o frame lógico fecha
self.end_frame(); // present/housekeeping if run.reason == crate::vm::LogicalFrameEndingReason::FrameSync {
self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64; // Calcula quanto tempo a "CPU simulada" trabalhou de verdade 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. /// Início do frame: zera flags transitórias.
pub fn begin_frame(&mut self, signals: &InputSignals) { 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. // 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. // 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(); self.audio.clear_commands();

View File

@ -4,7 +4,8 @@ mod opcode;
mod call_frame; mod call_frame;
mod program; mod program;
pub use value::Value;
pub use virtual_machine::VirtualMachine;
pub use opcode::OpCode; pub use opcode::OpCode;
pub use program::Program; pub use program::Program;
pub use value::Value;
pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine};

View File

@ -4,9 +4,22 @@ use crate::vm::call_frame::CallFrame;
use crate::vm::opcode::OpCode; use crate::vm::opcode::OpCode;
use crate::vm::value::Value; use crate::vm::value::Value;
use crate::vm::Program; 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 struct VirtualMachine {
pub pc: usize, pub pc: usize,
pub operand_stack: Vec<Value>, pub operand_stack: Vec<Value>,
@ -275,33 +288,53 @@ impl Default for VirtualMachine {
} }
impl VirtualMachine { impl VirtualMachine {
pub fn run_budget(&mut self, budget: u64, native: &mut dyn NativeInterface) -> Result<u64, String> { pub fn run_budget(
&mut self,
budget: u64,
native: &mut dyn NativeInterface
) -> Result<BudgetReport, String> {
let start_cycles = self.cycles; let start_cycles = self.cycles;
let mut budget_used = 0; let mut ending_reason: Option<LogicalFrameEndingReason> = 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 pc_before = self.pc;
let cycles_before = self.cycles; let cycles_before = self.cycles;
// Fast-path: FRAME_SYNC encerra o frame lógico
let opcode_val = self.peek_u16()?; let opcode_val = self.peek_u16()?;
let opcode = OpCode::try_from(opcode_val)?; let opcode = OpCode::try_from(opcode_val)?;
if opcode == OpCode::FrameSync { if opcode == OpCode::FrameSync {
self.pc += 2; self.pc += 2;
self.cycles += OpCode::FrameSync.cycles(); self.cycles += OpCode::FrameSync.cycles();
ending_reason = Some(LogicalFrameEndingReason::FrameSync);
break; break;
} }
self.step(native)?; 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 { if self.pc == pc_before && self.cycles == cycles_before && !self.halted {
return Err(format!("VM stuck at PC 0x{:08X}", self.pc)); 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<u16, String> { fn peek_u16(&self) -> Result<u16, String> {