diff --git a/crates/prometeu-core/src/model/cartridge.rs b/crates/prometeu-core/src/model/cartridge.rs index a721d150..1c0fd9b8 100644 --- a/crates/prometeu-core/src/model/cartridge.rs +++ b/crates/prometeu-core/src/model/cartridge.rs @@ -1,7 +1,14 @@ use crate::virtual_machine::Program; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AppMode { + Game, + System, +} + #[derive(Debug, Clone)] pub struct AppHeader { + pub mode: AppMode, pub app_id: String, pub magic: u32, pub version: u16, diff --git a/crates/prometeu-core/src/model/mod.rs b/crates/prometeu-core/src/model/mod.rs index 6a4a9073..b1b43dc4 100644 --- a/crates/prometeu-core/src/model/mod.rs +++ b/crates/prometeu-core/src/model/mod.rs @@ -8,7 +8,7 @@ mod sample; mod cartridge; pub use button::Button; -pub use cartridge::Cartridge; +pub use cartridge::{AppHeader, AppMode, Cartridge}; pub use color::Color; pub use sample::Sample; pub use sprite::Sprite; diff --git a/crates/prometeu-core/src/prometeu_os/prometeu_os.rs b/crates/prometeu-core/src/prometeu_os/prometeu_os.rs index 2ac945e5..1f8e425a 100644 --- a/crates/prometeu-core/src/prometeu_os/prometeu_os.rs +++ b/crates/prometeu-core/src/prometeu_os/prometeu_os.rs @@ -9,7 +9,8 @@ use std::sync::Arc; pub struct PrometeuOS { pub tick_index: u64, pub logical_frame_index: u64, - pub logical_frame_open: bool, + pub logical_frame_active: bool, + pub logical_frame_remaining_cycles: u64, pub last_frame_cpu_time_us: u64, // Assets de exemplo (mantidos para compatibilidade com syscalls de áudio v0) @@ -19,13 +20,15 @@ pub struct PrometeuOS { } impl PrometeuOS { - pub const CYCLES_PER_FRAME: u64 = 100_000; + pub const CYCLES_PER_LOGICAL_FRAME: u64 = 100_000; + pub const SLICE_PER_TICK: u64 = 100_000; // Por enquanto, o mesmo, mas permite granularidade diferente pub fn new() -> Self { let mut os = Self { tick_index: 0, logical_frame_index: 0, - logical_frame_open: false, + logical_frame_active: false, + logical_frame_remaining_cycles: 0, last_frame_cpu_time_us: 0, sample_square: None, sample_kick: None, @@ -42,7 +45,8 @@ impl PrometeuOS { *vm = VirtualMachine::default(); self.tick_index = 0; self.logical_frame_index = 0; - self.logical_frame_open = false; + self.logical_frame_active = false; + self.logical_frame_remaining_cycles = 0; self.last_frame_cpu_time_us = 0; } @@ -56,25 +60,34 @@ impl PrometeuOS { let start = std::time::Instant::now(); self.tick_index += 1; - if !self.logical_frame_open { - self.logical_frame_open = true; + if !self.logical_frame_active { + self.logical_frame_active = true; + self.logical_frame_remaining_cycles = Self::CYCLES_PER_LOGICAL_FRAME; self.begin_logical_frame(signals, hw); } - // Executa budget - let run_result = vm.run_budget(Self::CYCLES_PER_FRAME, self, hw); + // Budget para este tick: o mínimo entre a fatia do tick e o que resta no frame lógico + let budget = std::cmp::min(Self::SLICE_PER_TICK, self.logical_frame_remaining_cycles); - match run_result { - Ok(run) => { - if run.reason == crate::virtual_machine::LogicalFrameEndingReason::FrameSync { - hw.gfx_mut().render_all(); - self.end_logical_frame(hw); - self.logical_frame_index += 1; - self.logical_frame_open = false; + if budget > 0 { + // Executa budget + let run_result = vm.run_budget(budget, self, hw); + + match run_result { + Ok(run) => { + self.logical_frame_remaining_cycles = self.logical_frame_remaining_cycles.saturating_sub(run.cycles_used); + + if run.reason == crate::virtual_machine::LogicalFrameEndingReason::FrameSync { + hw.gfx_mut().render_all(); + self.end_logical_frame(hw); + self.logical_frame_index += 1; + self.logical_frame_active = false; + self.logical_frame_remaining_cycles = 0; + } + } + Err(e) => { + return Some(format!("PVM Fault: {:?}", e)); } - } - Err(e) => { - return Some(format!("PVM Fault: {:?}", e)); } } @@ -150,6 +163,98 @@ impl PrometeuOS { } } +#[cfg(test)] +mod tests { + use super::*; + use crate::hardware::InputSignals; + use crate::model::{AppHeader, AppMode, Cartridge}; + use crate::virtual_machine::{Program, VirtualMachine}; + use crate::Hardware; + + #[test] + fn test_infinite_loop_budget_reset_bug() { + let mut os = PrometeuOS::new(); + let mut vm = VirtualMachine::default(); + let mut hw = Hardware::new(); + let signals = InputSignals::default(); + + // JMP 0 (Loop infinito) + // OpCode::Jmp = 0x02, seguido por u32 0 (0x00, 0x00, 0x00, 0x00) + let rom = vec![0x02, 0x00, 0x00, 0x00, 0x00, 0x00]; + let program = Program::new(rom, vec![]); + let cartridge = Cartridge { + header: AppHeader { + mode: AppMode::Game, + app_id: "test".to_string(), + magic: 0, + version: 1, + title: "test".to_string(), + entrypoint: 0, + }, + program, + }; + os.initialize_vm(&mut vm, &cartridge); + + // Primeiro tick + os.step_frame(&mut vm, &signals, &mut hw); + let cycles_after_tick_1 = vm.cycles; + assert!(cycles_after_tick_1 >= PrometeuOS::CYCLES_PER_LOGICAL_FRAME); + + // Segundo tick - Agora ele NÃO deve ganhar mais budget + os.step_frame(&mut vm, &signals, &mut hw); + let cycles_after_tick_2 = vm.cycles; + + // CORREÇÃO: Ele não deve ter consumido ciclos no segundo tick porque o budget do frame lógico acabou + println!("Cycles after tick 1: {}, tick 2: {}", cycles_after_tick_1, cycles_after_tick_2); + assert_eq!(cycles_after_tick_2, cycles_after_tick_1, "VM should NOT have consumed more cycles in the second tick because logical frame budget is exhausted"); + } + + #[test] + fn test_budget_reset_on_frame_sync() { + let mut os = PrometeuOS::new(); + let mut vm = VirtualMachine::default(); + let mut hw = Hardware::new(); + let signals = InputSignals::default(); + + // Loop que chama FrameSync: + // PUSH_CONST 0 (dummy) + // FrameSync (0x80) + // JMP 0 + let rom = vec![ + 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, // PUSH const 0 (2 bytes opcode + 4 bytes u32) + 0x80, 0x00, // FrameSync (2 bytes opcode) + 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, // JMP 0 (2 bytes opcode + 4 bytes u32) + ]; + let program = Program::new(rom, vec![Value::Integer(0)]); + let cartridge = Cartridge { + header: AppHeader { + mode: AppMode::Game, + app_id: "test".to_string(), + magic: 0, + version: 1, + title: "test".to_string(), + entrypoint: 0, + }, + program, + }; + os.initialize_vm(&mut vm, &cartridge); + + // Primeiro tick + os.step_frame(&mut vm, &signals, &mut hw); + let cycles_after_tick_1 = vm.cycles; + + // Deve ter parado no FrameSync + assert!(cycles_after_tick_1 > 0, "VM should have consumed some cycles"); + assert!(cycles_after_tick_1 < PrometeuOS::CYCLES_PER_LOGICAL_FRAME); + + // Segundo tick - Deve resetar o budget e rodar mais um pouco até o próximo FrameSync + os.step_frame(&mut vm, &signals, &mut hw); + let cycles_after_tick_2 = vm.cycles; + + assert!(cycles_after_tick_2 > cycles_after_tick_1, "VM should have consumed more cycles because FrameSync reset the budget"); + } +} + impl NativeInterface for PrometeuOS { fn syscall(&mut self, id: u32, vm: &mut VirtualMachine, hw: &mut dyn HardwareBridge) -> Result { match id { diff --git a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs index 94240f09..aa59911f 100644 --- a/crates/prometeu-core/src/virtual_machine/virtual_machine.rs +++ b/crates/prometeu-core/src/virtual_machine/virtual_machine.rs @@ -51,6 +51,7 @@ impl VirtualMachine { self.call_stack.clear(); self.globals.clear(); self.heap.clear(); + self.cycles = 0; self.halted = false; } }