PR-04 - CAP fixes
This commit is contained in:
parent
293d1029a2
commit
e519408f51
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<u64, String> {
|
||||
match id {
|
||||
|
||||
@ -51,6 +51,7 @@ impl VirtualMachine {
|
||||
self.call_stack.clear();
|
||||
self.globals.clear();
|
||||
self.heap.clear();
|
||||
self.cycles = 0;
|
||||
self.halted = false;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user