logical frame computed by cost
This commit is contained in:
parent
85a5f2d63f
commit
43e366bcd5
@ -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();
|
||||||
|
|||||||
@ -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};
|
||||||
|
|
||||||
|
|||||||
@ -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> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user