implemeted firmware and hardware bridge
This commit is contained in:
parent
751619fb31
commit
ba488a5fee
77
crates/core/src/firmware/firmware.rs
Normal file
77
crates/core/src/firmware/firmware.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use crate::firmware::firmware_state::FirmwareState;
|
||||||
|
use crate::model::{AppMode, Cartridge, Color};
|
||||||
|
use crate::peripherals::InputSignals;
|
||||||
|
use crate::prometeu_hub::PrometeuHub;
|
||||||
|
use crate::prometeu_os::PrometeuOS;
|
||||||
|
|
||||||
|
pub struct Firmware {
|
||||||
|
pub os: PrometeuOS,
|
||||||
|
pub hub: PrometeuHub,
|
||||||
|
pub state: FirmwareState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Firmware {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
os: PrometeuOS::new(),
|
||||||
|
hub: PrometeuHub::new(),
|
||||||
|
state: FirmwareState::Reset,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn step_frame(&mut self, signals: &InputSignals) {
|
||||||
|
match &mut self.state {
|
||||||
|
FirmwareState::Reset => {
|
||||||
|
self.os.reset();
|
||||||
|
self.state = FirmwareState::SplashScreen;
|
||||||
|
}
|
||||||
|
FirmwareState::SplashScreen => {
|
||||||
|
// Splash logic opcional
|
||||||
|
self.state = FirmwareState::LaunchHubHome;
|
||||||
|
}
|
||||||
|
FirmwareState::LaunchHubHome => {
|
||||||
|
self.hub.init();
|
||||||
|
self.state = FirmwareState::HubHome;
|
||||||
|
}
|
||||||
|
FirmwareState::HubHome => {
|
||||||
|
// UI do Hub
|
||||||
|
self.hub.update(&mut self.os);
|
||||||
|
}
|
||||||
|
FirmwareState::PosRunGame(_cart) => {
|
||||||
|
if let Some(error) = self.os.step_frame(signals) {
|
||||||
|
self.state = FirmwareState::PosCrashScreen(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FirmwareState::PosRunSystem(_cart) => {
|
||||||
|
// System Apps rodam "dentro" do Hub/Window System
|
||||||
|
if let Some(error) = self.os.step_frame(signals) {
|
||||||
|
self.state = FirmwareState::PosCrashScreen(error);
|
||||||
|
}
|
||||||
|
// TODO: Compor com a UI do Hub
|
||||||
|
}
|
||||||
|
FirmwareState::PosCrashScreen(_error) => {
|
||||||
|
// Atualiza periféricos para input na tela de crash
|
||||||
|
self.os.hardware_mut().pad_mut().begin_frame(signals);
|
||||||
|
|
||||||
|
// Tela de erro: fundo vermelho, texto branco
|
||||||
|
self.os.hardware_mut().gfx_mut().clear(Color::RED);
|
||||||
|
// Por enquanto apenas logamos ou mostramos algo simples
|
||||||
|
// No futuro, usar draw_text
|
||||||
|
self.os.hardware_mut().gfx_mut().present();
|
||||||
|
|
||||||
|
// Se apertar START, volta pro Hub
|
||||||
|
if self.os.hardware().pad().start.down {
|
||||||
|
self.state = FirmwareState::LaunchHubHome;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_app(&mut self, cart: Cartridge, mode: AppMode) {
|
||||||
|
self.os.load_cartridge(&cart);
|
||||||
|
match mode {
|
||||||
|
AppMode::Game => self.state = FirmwareState::PosRunGame(cart),
|
||||||
|
AppMode::System => self.state = FirmwareState::PosRunSystem(cart),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
crates/core/src/firmware/firmware_state.rs
Normal file
12
crates/core/src/firmware/firmware_state.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use crate::model::Cartridge;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum FirmwareState {
|
||||||
|
Reset,
|
||||||
|
SplashScreen,
|
||||||
|
LaunchHubHome,
|
||||||
|
HubHome,
|
||||||
|
PosRunGame(Cartridge),
|
||||||
|
PosRunSystem(Cartridge),
|
||||||
|
PosCrashScreen(String),
|
||||||
|
}
|
||||||
4
crates/core/src/firmware/mod.rs
Normal file
4
crates/core/src/firmware/mod.rs
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
mod firmware;
|
||||||
|
pub mod firmware_state;
|
||||||
|
|
||||||
|
pub use firmware::Firmware;
|
||||||
@ -1,11 +0,0 @@
|
|||||||
pub struct Firmware {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Firmware {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1 +0,0 @@
|
|||||||
pub mod firmware;
|
|
||||||
57
crates/core/src/hardware.rs
Normal file
57
crates/core/src/hardware.rs
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
use crate::peripherals::{Audio, Gfx, InputSignals, Pad, Touch, HardwareBridge};
|
||||||
|
use crate::firmware::{Firmware};
|
||||||
|
|
||||||
|
pub struct Hardware {
|
||||||
|
pub firmware: Firmware,
|
||||||
|
pub gfx: Gfx,
|
||||||
|
pub audio: Audio,
|
||||||
|
pub pad: Pad,
|
||||||
|
pub touch: Touch,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HardwareBridge for Hardware {
|
||||||
|
fn gfx(&self) -> &Gfx { &self.gfx }
|
||||||
|
fn gfx_mut(&mut self) -> &mut Gfx { &mut self.gfx }
|
||||||
|
|
||||||
|
fn audio(&self) -> &Audio { &self.audio }
|
||||||
|
fn audio_mut(&mut self) -> &mut Audio { &mut self.audio }
|
||||||
|
|
||||||
|
fn pad(&self) -> &Pad { &self.pad }
|
||||||
|
fn pad_mut(&mut self) -> &mut Pad { &mut self.pad }
|
||||||
|
|
||||||
|
fn touch(&self) -> &Touch { &self.touch }
|
||||||
|
fn touch_mut(&mut self) -> &mut Touch { &mut self.touch }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Hardware {
|
||||||
|
pub const W: usize = 320;
|
||||||
|
pub const H: usize = 180;
|
||||||
|
|
||||||
|
pub fn new() -> Box<Self> {
|
||||||
|
let mut hw = Box::new(Self {
|
||||||
|
firmware: Firmware::new(),
|
||||||
|
gfx: Gfx::new(Self::W, Self::H),
|
||||||
|
audio: Audio::new(),
|
||||||
|
pad: Pad::default(),
|
||||||
|
touch: Touch::default(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Configura a referência circular (através do trait HardwareBridge)
|
||||||
|
let hw_ptr = hw.as_mut() as *mut dyn HardwareBridge;
|
||||||
|
hw.firmware.os.hw_ptr = Some(hw_ptr);
|
||||||
|
|
||||||
|
hw
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Atalhos para o Runner (desktop) não quebrar tanto,
|
||||||
|
/// delegando para o PrometeuOS onde os contadores agora vivem.
|
||||||
|
pub fn tick_index(&self) -> u64 { self.firmware.os.tick_index }
|
||||||
|
pub fn logical_frame_index(&self) -> u64 { self.firmware.os.logical_frame_index }
|
||||||
|
pub fn last_frame_cpu_time_us(&self) -> u64 { self.firmware.os.last_frame_cpu_time_us }
|
||||||
|
|
||||||
|
/// "Contrato de frame" do PROMETEU.
|
||||||
|
pub fn step_frame(&mut self, signals: &InputSignals) {
|
||||||
|
// O Host chama o hardware, que delega para o Firmware/OS.
|
||||||
|
self.firmware.step_frame(signals);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,8 +1,9 @@
|
|||||||
pub mod logical_hardware;
|
pub mod hardware;
|
||||||
pub mod peripherals;
|
pub mod peripherals;
|
||||||
pub mod vm;
|
pub mod vm;
|
||||||
mod model;
|
mod model;
|
||||||
mod native_interface;
|
pub mod firmware;
|
||||||
mod utilz;
|
mod prometeu_os;
|
||||||
|
mod prometeu_hub;
|
||||||
|
|
||||||
pub use logical_hardware::LogicalHardware;
|
pub use hardware::Hardware;
|
||||||
|
|||||||
@ -1,165 +0,0 @@
|
|||||||
use crate::model::{Cartridge, Color, Sample};
|
|
||||||
use crate::peripherals::{Audio, Gfx, InputSignals, Pad, Touch};
|
|
||||||
use crate::vm::VirtualMachine;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// PROMETEU "hardware lógico" (v0).
|
|
||||||
/// O Host alimenta INPUT SIGNALS e chama `step_frame()` em 60Hz.
|
|
||||||
pub struct LogicalHardware {
|
|
||||||
pub gfx: Gfx,
|
|
||||||
pub audio: Audio,
|
|
||||||
pub pad: Pad,
|
|
||||||
pub touch: Touch,
|
|
||||||
pub tick_index: u64,
|
|
||||||
pub logical_frame_index: u64,
|
|
||||||
pub last_frame_cpu_time_us: u64,
|
|
||||||
pub vm: VirtualMachine,
|
|
||||||
pub cartridge: Option<Cartridge>,
|
|
||||||
|
|
||||||
// Estado Interno
|
|
||||||
logical_frame_open: bool,
|
|
||||||
|
|
||||||
// Assets de exemplo
|
|
||||||
pub sample_square: Option<Arc<Sample>>,
|
|
||||||
pub sample_kick: Option<Arc<Sample>>,
|
|
||||||
pub sample_snare: Option<Arc<Sample>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LogicalHardware {
|
|
||||||
pub const W: usize = 320;
|
|
||||||
pub const H: usize = 180;
|
|
||||||
pub const CYCLES_PER_FRAME: u64 = 100_000; // Exemplo de budget
|
|
||||||
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let mut logical_hardware = Self {
|
|
||||||
gfx: Gfx::new(Self::W, Self::H),
|
|
||||||
audio: Audio::new(),
|
|
||||||
pad: Pad::default(),
|
|
||||||
touch: Touch::default(),
|
|
||||||
tick_index: 0,
|
|
||||||
logical_frame_index: 0,
|
|
||||||
last_frame_cpu_time_us: 0,
|
|
||||||
vm: VirtualMachine::default(),
|
|
||||||
cartridge: None,
|
|
||||||
logical_frame_open: false,
|
|
||||||
sample_square: None,
|
|
||||||
sample_kick: None,
|
|
||||||
sample_snare: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Inicializa samples básicos
|
|
||||||
logical_hardware.sample_square = Some(Arc::new(Self::create_square_sample(440.0, 0.1)));
|
|
||||||
|
|
||||||
logical_hardware
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_color(&self, index: usize) -> Color {
|
|
||||||
// Implementação simplificada: se houver bancos, usa a paleta do banco 0
|
|
||||||
// Caso contrário, usa cores básicas fixas
|
|
||||||
if let Some(bank) = self.gfx.banks[0].as_ref() {
|
|
||||||
bank.palettes[0][index % 16]
|
|
||||||
} else {
|
|
||||||
match index % 16 {
|
|
||||||
0 => Color::BLACK,
|
|
||||||
1 => Color::WHITE,
|
|
||||||
2 => Color::RED,
|
|
||||||
3 => Color::GREEN,
|
|
||||||
4 => Color::BLUE,
|
|
||||||
5 => Color::YELLOW,
|
|
||||||
6 => Color::CYAN,
|
|
||||||
7 => Color::INDIGO,
|
|
||||||
8 => Color::GRAY,
|
|
||||||
_ => Color::BLACK,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_button_down(&self, id: u32) -> bool {
|
|
||||||
match id {
|
|
||||||
0 => self.pad.up.down,
|
|
||||||
1 => self.pad.down.down,
|
|
||||||
2 => self.pad.left.down,
|
|
||||||
3 => self.pad.right.down,
|
|
||||||
4 => self.pad.a.down,
|
|
||||||
5 => self.pad.b.down,
|
|
||||||
6 => self.pad.x.down,
|
|
||||||
7 => self.pad.y.down,
|
|
||||||
8 => self.pad.l.down,
|
|
||||||
9 => self.pad.r.down,
|
|
||||||
10 => self.pad.start.down,
|
|
||||||
11 => self.pad.select.down,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_cartridge(&mut self, cart: Cartridge) {
|
|
||||||
self.cartridge = Some(cart);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_square_sample(freq: f64, duration: f64) -> Sample {
|
|
||||||
let sample_rate = crate::peripherals::OUTPUT_SAMPLE_RATE;
|
|
||||||
let num_samples = (duration * sample_rate as f64) as usize;
|
|
||||||
let mut data = Vec::with_capacity(num_samples);
|
|
||||||
let period = sample_rate as f64 / freq;
|
|
||||||
|
|
||||||
for i in 0..num_samples {
|
|
||||||
let val = if (i as f64 % period) < (period / 2.0) {
|
|
||||||
10000 // Volume médio
|
|
||||||
} else {
|
|
||||||
-10000
|
|
||||||
};
|
|
||||||
data.push(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
Sample::new(sample_rate, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// "Contrato de frame" do PROMETEU (Template Method sem loop).
|
|
||||||
/// 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.tick_index += 1; // não importa o frame lógico, o tick sempre incrementa
|
|
||||||
|
|
||||||
// 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 run = vm
|
|
||||||
.run_budget(Self::CYCLES_PER_FRAME, self)
|
|
||||||
.expect("vm error");
|
|
||||||
self.vm = vm;
|
|
||||||
|
|
||||||
// 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.logical_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) {
|
|
||||||
// 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();
|
|
||||||
|
|
||||||
// Flags transitórias do TOUCH devem durar 1 frame.
|
|
||||||
self.touch.begin_frame(signals);
|
|
||||||
|
|
||||||
// Se você quiser input com pressed/released depois:
|
|
||||||
self.pad.begin_frame(signals);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Final do frame: troca buffers.
|
|
||||||
pub fn end_frame(&mut self) {
|
|
||||||
self.gfx.present();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +1,27 @@
|
|||||||
use crate::vm::Program;
|
use crate::vm::Program;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum AppMode {
|
||||||
|
Game,
|
||||||
|
System,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct AppHeader {
|
||||||
|
pub app_id: String,
|
||||||
|
pub title: String,
|
||||||
|
pub mode: AppMode,
|
||||||
|
pub entrypoint: u32,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Cartridge {
|
pub struct Cartridge {
|
||||||
|
pub header: AppHeader,
|
||||||
pub program: Program,
|
pub program: Program,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cartridge {
|
impl Cartridge {
|
||||||
pub fn new(program: Program) -> Self {
|
pub fn new(header: AppHeader, program: Program) -> Self {
|
||||||
Self { program }
|
Self { header, program }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,4 +14,4 @@ pub use tile_bank::{TileBank, TileSize};
|
|||||||
pub use tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap};
|
pub use tile_layer::{HudTileLayer, ScrollableTileLayer, TileMap};
|
||||||
pub use sprite::Sprite;
|
pub use sprite::Sprite;
|
||||||
pub use sample::Sample;
|
pub use sample::Sample;
|
||||||
pub use cartridge::Cartridge;
|
pub use cartridge::{Cartridge, AppMode};
|
||||||
|
|||||||
@ -10,3 +10,19 @@ pub use input_signal::InputSignals;
|
|||||||
pub use pad::Pad;
|
pub use pad::Pad;
|
||||||
pub use touch::Touch;
|
pub use touch::Touch;
|
||||||
pub use audio::{Audio, Channel, AudioCommand, LoopMode, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
|
pub use audio::{Audio, Channel, AudioCommand, LoopMode, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
|
||||||
|
|
||||||
|
/// Interface de acesso ao hardware para o Firmware/OS.
|
||||||
|
/// Centraliza as chamadas para periféricos sem expor a estrutura interna do HardwareBridge.
|
||||||
|
pub trait HardwareBridge {
|
||||||
|
fn gfx(&self) -> &Gfx;
|
||||||
|
fn gfx_mut(&mut self) -> &mut Gfx;
|
||||||
|
|
||||||
|
fn audio(&self) -> &Audio;
|
||||||
|
fn audio_mut(&mut self) -> &mut Audio;
|
||||||
|
|
||||||
|
fn pad(&self) -> &Pad;
|
||||||
|
fn pad_mut(&mut self) -> &mut Pad;
|
||||||
|
|
||||||
|
fn touch(&self) -> &Touch;
|
||||||
|
fn touch_mut(&mut self) -> &mut Touch;
|
||||||
|
}
|
||||||
|
|||||||
3
crates/core/src/prometeu_hub/mod.rs
Normal file
3
crates/core/src/prometeu_hub/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
mod prometeu_hub;
|
||||||
|
|
||||||
|
pub use prometeu_hub::PrometeuHub;
|
||||||
40
crates/core/src/prometeu_hub/prometeu_hub.rs
Normal file
40
crates/core/src/prometeu_hub/prometeu_hub.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use crate::model::Color;
|
||||||
|
use crate::prometeu_os::PrometeuOS;
|
||||||
|
|
||||||
|
// TODO: Mover para arquivo próprio se crescer.
|
||||||
|
/// Sistema de janelas do PROMETEU.
|
||||||
|
pub struct WindowSystem {
|
||||||
|
pub theme_color: Color,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowSystem {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
theme_color: Color::INDIGO,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// PrometeuHub: Launcher e ambiente de UI do sistema.
|
||||||
|
pub struct PrometeuHub {
|
||||||
|
pub window_system: WindowSystem,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrometeuHub {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
window_system: WindowSystem::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(&mut self) {
|
||||||
|
// Inicializa o Window System e lista apps
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(&mut self, os: &mut PrometeuOS) {
|
||||||
|
// Renderiza a UI básica do Hub
|
||||||
|
os.hardware_mut().gfx_mut().clear(self.window_system.theme_color);
|
||||||
|
// TODO: Desenhar lista de apps
|
||||||
|
os.hardware_mut().gfx_mut().present();
|
||||||
|
}
|
||||||
|
}
|
||||||
5
crates/core/src/prometeu_os/mod.rs
Normal file
5
crates/core/src/prometeu_os/mod.rs
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
mod prometeu_os;
|
||||||
|
mod native_interface;
|
||||||
|
|
||||||
|
pub use prometeu_os::PrometeuOS;
|
||||||
|
pub use native_interface::NativeInterface;
|
||||||
@ -1,28 +1,22 @@
|
|||||||
|
use crate::prometeu_os::prometeu_os::PrometeuOS;
|
||||||
use crate::vm::{Value, VirtualMachine};
|
use crate::vm::{Value, VirtualMachine};
|
||||||
use crate::LogicalHardware;
|
|
||||||
|
|
||||||
pub trait NativeInterface {
|
pub trait NativeInterface {
|
||||||
fn syscall(&mut self, id: u32, vm: &mut VirtualMachine) -> Result<u64, String>;
|
fn syscall(&mut self, id: u32, vm: &mut VirtualMachine) -> Result<u64, String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NativeInterface for LogicalHardware {
|
impl NativeInterface for PrometeuOS {
|
||||||
fn syscall(&mut self, id: u32, vm: &mut VirtualMachine) -> Result<u64, String> {
|
fn syscall(&mut self, id: u32, vm: &mut VirtualMachine) -> Result<u64, String> {
|
||||||
match id {
|
match id {
|
||||||
// system.has_cart() -> bool
|
// system.has_cart() -> bool
|
||||||
0x0001 => {
|
0x0001 => {
|
||||||
vm.push(Value::Boolean(self.cartridge.is_some()));
|
vm.push(Value::Boolean(self.current_cartridge.is_some()));
|
||||||
Ok(10)
|
Ok(10)
|
||||||
}
|
}
|
||||||
// system.run_cart()
|
// system.run_cart()
|
||||||
0x0002 => {
|
0x0002 => {
|
||||||
if let Some(cart) = self.cartridge.as_ref() {
|
if let Some(cart) = self.current_cartridge.as_ref().cloned() {
|
||||||
vm.program = cart.program.clone();
|
self.load_cartridge(&cart);
|
||||||
vm.pc = 0;
|
|
||||||
vm.operand_stack.clear();
|
|
||||||
vm.call_stack.clear();
|
|
||||||
vm.globals.clear();
|
|
||||||
vm.heap.clear();
|
|
||||||
vm.halted = false;
|
|
||||||
} else {
|
} else {
|
||||||
return Err("No cartridge inserted".into());
|
return Err("No cartridge inserted".into());
|
||||||
}
|
}
|
||||||
@ -32,7 +26,7 @@ impl NativeInterface for LogicalHardware {
|
|||||||
0x1001 => {
|
0x1001 => {
|
||||||
let color_idx = vm.pop_integer()? as usize;
|
let color_idx = vm.pop_integer()? as usize;
|
||||||
let color = self.get_color(color_idx);
|
let color = self.get_color(color_idx);
|
||||||
self.gfx.clear(color);
|
self.hardware_mut().gfx_mut().clear(color);
|
||||||
Ok(100)
|
Ok(100)
|
||||||
}
|
}
|
||||||
// gfx.draw_rect(x, y, w, h, color_index)
|
// gfx.draw_rect(x, y, w, h, color_index)
|
||||||
@ -43,7 +37,7 @@ impl NativeInterface for LogicalHardware {
|
|||||||
let y = vm.pop_integer()? as i32;
|
let y = vm.pop_integer()? as i32;
|
||||||
let x = vm.pop_integer()? as i32;
|
let x = vm.pop_integer()? as i32;
|
||||||
let color = self.get_color(color_idx);
|
let color = self.get_color(color_idx);
|
||||||
self.gfx.fill_rect(x, y, w, h, color);
|
self.hardware_mut().gfx_mut().fill_rect(x, y, w, h, color);
|
||||||
Ok(200)
|
Ok(200)
|
||||||
}
|
}
|
||||||
// input.get_pad(button_id) -> bool
|
// input.get_pad(button_id) -> bool
|
||||||
@ -54,7 +48,6 @@ impl NativeInterface for LogicalHardware {
|
|||||||
Ok(50)
|
Ok(50)
|
||||||
}
|
}
|
||||||
// audio.play_sample(sample_id, voice_id, volume, pan, pitch)
|
// audio.play_sample(sample_id, voice_id, volume, pan, pitch)
|
||||||
// sample_id: 0=square, 1=kick, 2=snare
|
|
||||||
0x3001 => {
|
0x3001 => {
|
||||||
let pitch = vm.pop_number()?;
|
let pitch = vm.pop_number()?;
|
||||||
let pan = vm.pop_integer()? as u8;
|
let pan = vm.pop_integer()? as u8;
|
||||||
@ -70,7 +63,7 @@ impl NativeInterface for LogicalHardware {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(s) = sample {
|
if let Some(s) = sample {
|
||||||
self.audio.play(s, voice_id, volume, pan, pitch, 0, crate::peripherals::LoopMode::Off);
|
self.hardware_mut().audio_mut().play(s, voice_id, volume, pan, pitch, 0, crate::peripherals::LoopMode::Off);
|
||||||
}
|
}
|
||||||
Ok(300)
|
Ok(300)
|
||||||
}
|
}
|
||||||
182
crates/core/src/prometeu_os/prometeu_os.rs
Normal file
182
crates/core/src/prometeu_os/prometeu_os.rs
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
use crate::model::{Cartridge, Color, Sample};
|
||||||
|
use crate::peripherals::{HardwareBridge, InputSignals};
|
||||||
|
use crate::vm::VirtualMachine;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// PrometeuOS (POS): O firmware/base do sistema.
|
||||||
|
/// Autoridade máxima do boot, periféricos, execução da PVM e tratamento de falhas.
|
||||||
|
pub struct PrometeuOS {
|
||||||
|
pub vm: VirtualMachine,
|
||||||
|
pub tick_index: u64,
|
||||||
|
pub logical_frame_index: u64,
|
||||||
|
pub logical_frame_open: bool,
|
||||||
|
pub last_frame_cpu_time_us: u64,
|
||||||
|
pub hw_ptr: Option<*mut dyn HardwareBridge>,
|
||||||
|
pub current_cartridge: Option<Cartridge>,
|
||||||
|
|
||||||
|
// Assets de exemplo (mantidos para compatibilidade com syscalls de áudio v0)
|
||||||
|
pub sample_square: Option<Arc<Sample>>,
|
||||||
|
pub sample_kick: Option<Arc<Sample>>,
|
||||||
|
pub sample_snare: Option<Arc<Sample>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PrometeuOS {
|
||||||
|
pub const CYCLES_PER_FRAME: u64 = 100_000;
|
||||||
|
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut os = Self {
|
||||||
|
vm: VirtualMachine::default(),
|
||||||
|
hw_ptr: None,
|
||||||
|
tick_index: 0,
|
||||||
|
logical_frame_index: 0,
|
||||||
|
logical_frame_open: false,
|
||||||
|
last_frame_cpu_time_us: 0,
|
||||||
|
current_cartridge: None,
|
||||||
|
sample_square: None,
|
||||||
|
sample_kick: None,
|
||||||
|
sample_snare: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inicializa samples básicos (mesma lógica do LogicalHardware)
|
||||||
|
os.sample_square = Some(Arc::new(Self::create_square_sample(440.0, 0.1)));
|
||||||
|
|
||||||
|
os
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper para acessar o hardware de forma segura (dentro do contexto do firmware).
|
||||||
|
#[inline]
|
||||||
|
pub fn hardware(&self) -> &dyn HardwareBridge {
|
||||||
|
unsafe { &*self.hw_ptr.expect("Hardware pointer not initialized") }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper para acessar o hardware de forma mutável.
|
||||||
|
#[inline]
|
||||||
|
pub fn hardware_mut(&mut self) -> &mut dyn HardwareBridge {
|
||||||
|
unsafe { &mut *self.hw_ptr.expect("Hardware pointer not initialized") }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.vm = VirtualMachine::default();
|
||||||
|
self.tick_index = 0;
|
||||||
|
self.logical_frame_index = 0;
|
||||||
|
self.logical_frame_open = false;
|
||||||
|
self.last_frame_cpu_time_us = 0;
|
||||||
|
self.current_cartridge = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Carrega um cartucho na PVM e reseta o estado de execução.
|
||||||
|
pub fn load_cartridge(&mut self, cart: &Cartridge) {
|
||||||
|
// Na spec: "resetar PC/stack/heap ao iniciar app"
|
||||||
|
self.current_cartridge = Some(cart.clone());
|
||||||
|
self.vm.program = cart.program.clone();
|
||||||
|
self.vm.pc = 0;
|
||||||
|
self.vm.operand_stack.clear();
|
||||||
|
self.vm.call_stack.clear();
|
||||||
|
self.vm.globals.clear();
|
||||||
|
self.vm.heap.clear();
|
||||||
|
self.vm.halted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Executa um tick do host (60Hz).
|
||||||
|
pub fn step_frame(&mut self, signals: &InputSignals) -> Option<String> {
|
||||||
|
let start = std::time::Instant::now();
|
||||||
|
self.tick_index += 1;
|
||||||
|
|
||||||
|
if !self.logical_frame_open {
|
||||||
|
self.logical_frame_open = true;
|
||||||
|
self.begin_logical_frame(signals);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executa budget
|
||||||
|
let mut vm = std::mem::take(&mut self.vm);
|
||||||
|
let run_result = vm.run_budget(Self::CYCLES_PER_FRAME, self);
|
||||||
|
self.vm = vm;
|
||||||
|
|
||||||
|
match run_result {
|
||||||
|
Ok(run) => {
|
||||||
|
if run.reason == crate::vm::LogicalFrameEndingReason::FrameSync {
|
||||||
|
self.hardware_mut().gfx_mut().render_all();
|
||||||
|
self.end_logical_frame();
|
||||||
|
self.logical_frame_index += 1;
|
||||||
|
self.logical_frame_open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
return Some(format!("PVM Fault: {:?}", e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.last_frame_cpu_time_us = start.elapsed().as_micros() as u64;
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn begin_logical_frame(&mut self, signals: &InputSignals) {
|
||||||
|
let hw = self.hardware_mut();
|
||||||
|
hw.audio_mut().clear_commands();
|
||||||
|
hw.touch_mut().begin_frame(signals);
|
||||||
|
hw.pad_mut().begin_frame(signals);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn end_logical_frame(&mut self) {
|
||||||
|
self.hardware_mut().gfx_mut().present();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper para syscalls
|
||||||
|
pub fn get_color(&self, index: usize) -> Color {
|
||||||
|
let hw = self.hardware();
|
||||||
|
if let Some(bank) = hw.gfx().banks[0].as_ref() {
|
||||||
|
bank.palettes[0][index % 16]
|
||||||
|
} else {
|
||||||
|
match index % 16 {
|
||||||
|
0 => Color::BLACK,
|
||||||
|
1 => Color::WHITE,
|
||||||
|
2 => Color::RED,
|
||||||
|
3 => Color::GREEN,
|
||||||
|
4 => Color::BLUE,
|
||||||
|
5 => Color::YELLOW,
|
||||||
|
6 => Color::CYAN,
|
||||||
|
7 => Color::INDIGO,
|
||||||
|
8 => Color::GRAY,
|
||||||
|
_ => Color::BLACK,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper para syscalls
|
||||||
|
pub fn is_button_down(&self, id: u32) -> bool {
|
||||||
|
let hw = self.hardware();
|
||||||
|
match id {
|
||||||
|
0 => hw.pad().up.down,
|
||||||
|
1 => hw.pad().down.down,
|
||||||
|
2 => hw.pad().left.down,
|
||||||
|
3 => hw.pad().right.down,
|
||||||
|
4 => hw.pad().a.down,
|
||||||
|
5 => hw.pad().b.down,
|
||||||
|
6 => hw.pad().x.down,
|
||||||
|
7 => hw.pad().y.down,
|
||||||
|
8 => hw.pad().l.down,
|
||||||
|
9 => hw.pad().r.down,
|
||||||
|
10 => hw.pad().start.down,
|
||||||
|
11 => hw.pad().select.down,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_square_sample(freq: f64, duration: f64) -> Sample {
|
||||||
|
let sample_rate = crate::peripherals::OUTPUT_SAMPLE_RATE;
|
||||||
|
let num_samples = (duration * sample_rate as f64) as usize;
|
||||||
|
let mut data = Vec::with_capacity(num_samples);
|
||||||
|
let period = sample_rate as f64 / freq;
|
||||||
|
|
||||||
|
for i in 0..num_samples {
|
||||||
|
let val = if (i as f64 % period) < (period / 2.0) {
|
||||||
|
10000
|
||||||
|
} else {
|
||||||
|
-10000
|
||||||
|
};
|
||||||
|
data.push(val);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sample::new(sample_rate, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,18 +0,0 @@
|
|||||||
use crate::vm::OpCode;
|
|
||||||
|
|
||||||
pub fn emit_u16(rom: &mut Vec<u8>, val: u16) {
|
|
||||||
rom.extend_from_slice(&val.to_le_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn emit_u32(rom: &mut Vec<u8>, val: u32) {
|
|
||||||
rom.extend_from_slice(&val.to_le_bytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn emit_op(rom: &mut Vec<u8>, op: OpCode) {
|
|
||||||
emit_u16(rom, op as u16);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn patch_u32(rom: &mut [u8], offset: usize, val: u32) {
|
|
||||||
let bytes = val.to_le_bytes();
|
|
||||||
rom[offset..offset + 4].copy_from_slice(&bytes);
|
|
||||||
}
|
|
||||||
@ -1,9 +1,7 @@
|
|||||||
use crate::native_interface::NativeInterface;
|
use crate::prometeu_os::NativeInterface;
|
||||||
use crate::utilz;
|
|
||||||
use crate::vm::call_frame::CallFrame;
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
@ -44,246 +42,11 @@ impl VirtualMachine {
|
|||||||
halted: false,
|
halted: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cria uma VM com um "Boot ROM" básico que exibe uma animação de padrão de teste.
|
|
||||||
/// Isso garante que a máquina sempre mostre algo ao ser ligada, mesmo sem cartucho.
|
|
||||||
pub fn new_boot_rom() -> Self {
|
|
||||||
let mut rom = Vec::new();
|
|
||||||
let mut constant_pool = Vec::new();
|
|
||||||
|
|
||||||
// Constant Pool
|
|
||||||
constant_pool.push(Value::Integer(160)); // 0: Center X
|
|
||||||
constant_pool.push(Value::Integer(90)); // 1: Center Y
|
|
||||||
constant_pool.push(Value::Integer(40)); // 2: Logo Size
|
|
||||||
constant_pool.push(Value::Integer(7)); // 3: Indigo (Logo)
|
|
||||||
constant_pool.push(Value::Integer(0)); // 4: Black (Background)
|
|
||||||
constant_pool.push(Value::Integer(180)); // 5: Splash Timeout (3s)
|
|
||||||
constant_pool.push(Value::Integer(2)); // 6: Divisor
|
|
||||||
constant_pool.push(Value::Integer(1)); // 7: 1
|
|
||||||
constant_pool.push(Value::Integer(10)); // 8: Start Button
|
|
||||||
constant_pool.push(Value::Integer(0)); // 9: Square Sample
|
|
||||||
constant_pool.push(Value::Integer(255)); // 10: Volume
|
|
||||||
constant_pool.push(Value::Integer(127)); // 11: Pan
|
|
||||||
constant_pool.push(Value::Float(1.0)); // 12: Pitch
|
|
||||||
constant_pool.push(Value::Integer(0)); // 13: Voice ID
|
|
||||||
constant_pool.push(Value::Integer(0)); // 14: Zero
|
|
||||||
constant_pool.push(Value::Integer(6)); // 15: Cyan (SO Logo)
|
|
||||||
|
|
||||||
// -- INICIALIZAÇÃO --
|
|
||||||
|
|
||||||
// G0 = 0 (Contador de frames)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 14);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::SetGlobal);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
|
|
||||||
// G1 = 0 (Estado BIOS: 0=Splash, 1=SO)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 14);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::SetGlobal);
|
|
||||||
utilz::emit_u32(&mut rom, 1);
|
|
||||||
|
|
||||||
// Toca o som de boot "plim"
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 9); // sample
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 13); // voice
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 10); // vol
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 11); // pan
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 12); // pitch
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut rom, 0x3001);
|
|
||||||
|
|
||||||
// Escopo local para cálculos temporários [0:f, 1:s, 2:x, 3:y]
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushScope);
|
|
||||||
utilz::emit_u32(&mut rom, 4);
|
|
||||||
|
|
||||||
let loop_start = rom.len() as u32;
|
|
||||||
|
|
||||||
// Limpa tela
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 4); // black
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut rom, 0x1001);
|
|
||||||
|
|
||||||
// --- MÁQUINA DE ESTADOS (G1) ---
|
|
||||||
utilz::emit_op(&mut rom, OpCode::GetGlobal);
|
|
||||||
utilz::emit_u32(&mut rom, 1); // G1
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 14); // 0
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Eq);
|
|
||||||
let jmp_to_so = rom.len();
|
|
||||||
utilz::emit_op(&mut rom, OpCode::JmpIfFalse);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
|
|
||||||
// === ESTADO 0: SPLASH ===
|
|
||||||
|
|
||||||
// 1. Cálculos da Animação
|
|
||||||
utilz::emit_op(&mut rom, OpCode::GetGlobal);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::SetLocal);
|
|
||||||
utilz::emit_u32(&mut rom, 0); // L0 = f
|
|
||||||
|
|
||||||
// S = if f < 40 { f } else { 40 }
|
|
||||||
utilz::emit_op(&mut rom, OpCode::GetLocal);
|
|
||||||
utilz::emit_u32(&mut rom, 0); // f
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 2); // 40
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Lt);
|
|
||||||
let jmp_s_full = rom.len();
|
|
||||||
utilz::emit_op(&mut rom, OpCode::JmpIfFalse);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
|
|
||||||
utilz::emit_op(&mut rom, OpCode::GetLocal);
|
|
||||||
utilz::emit_u32(&mut rom, 0); // f
|
|
||||||
let jmp_s_set = rom.len();
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Jmp);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
|
|
||||||
let s_full_addr = rom.len() as u32;
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 2); // 40
|
|
||||||
|
|
||||||
let s_set_addr = rom.len() as u32;
|
|
||||||
utilz::patch_u32(&mut rom, jmp_s_full + 2, s_full_addr);
|
|
||||||
utilz::patch_u32(&mut rom, jmp_s_set + 2, s_set_addr);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::SetLocal);
|
|
||||||
utilz::emit_u32(&mut rom, 1); // L1 = S
|
|
||||||
|
|
||||||
// X = 160 - S/2, Y = 90 - S/2
|
|
||||||
for (local_idx, const_idx) in [(2, 0), (3, 1)] {
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, const_idx);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::GetLocal);
|
|
||||||
utilz::emit_u32(&mut rom, 1); // S
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 6); // 2
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Div);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Sub);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::SetLocal);
|
|
||||||
utilz::emit_u32(&mut rom, local_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Desenha Quadrado (Logo)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::GetLocal);
|
|
||||||
utilz::emit_u32(&mut rom, 2); // X
|
|
||||||
utilz::emit_op(&mut rom, OpCode::GetLocal);
|
|
||||||
utilz::emit_u32(&mut rom, 3); // Y
|
|
||||||
utilz::emit_op(&mut rom, OpCode::GetLocal);
|
|
||||||
utilz::emit_u32(&mut rom, 1); // W
|
|
||||||
utilz::emit_op(&mut rom, OpCode::GetLocal);
|
|
||||||
utilz::emit_u32(&mut rom, 1); // H
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 3); // Indigo
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut rom, 0x1002);
|
|
||||||
|
|
||||||
// Checagem de Saída do Splash (f >= 180 OR START)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::GetGlobal);
|
|
||||||
utilz::emit_u32(&mut rom, 0); // f
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 5); // 180
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Gt); // f > 180 (ou eq, mas gt é mais seguro)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 8); // Start
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut rom, 0x2001); // get_pad
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Or);
|
|
||||||
let jmp_splash_continue = rom.len();
|
|
||||||
utilz::emit_op(&mut rom, OpCode::JmpIfFalse);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
|
|
||||||
// Validação de Cartucho
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut rom, 0x0001); // has_cart?
|
|
||||||
let jmp_no_cart_init = rom.len();
|
|
||||||
utilz::emit_op(&mut rom, OpCode::JmpIfFalse);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut rom, 0x0002); // run_cart
|
|
||||||
|
|
||||||
// Senão tem cartucho: vai para o SO
|
|
||||||
let no_cart_init_addr = rom.len() as u32;
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 7); // 1
|
|
||||||
utilz::emit_op(&mut rom, OpCode::SetGlobal);
|
|
||||||
utilz::emit_u32(&mut rom, 1); // G1 = 1 (SO)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 14); // 0
|
|
||||||
utilz::emit_op(&mut rom, OpCode::SetGlobal);
|
|
||||||
utilz::emit_u32(&mut rom, 0); // f = 0 (reset counter for SO)
|
|
||||||
|
|
||||||
let splash_continue_addr = rom.len() as u32;
|
|
||||||
utilz::patch_u32(&mut rom, jmp_splash_continue + 2, splash_continue_addr);
|
|
||||||
utilz::patch_u32(&mut rom, jmp_no_cart_init + 2, no_cart_init_addr);
|
|
||||||
|
|
||||||
let jmp_end_state = rom.len();
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Jmp);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
|
|
||||||
// === ESTADO 1: SO ===
|
|
||||||
let so_state_addr = rom.len() as u32;
|
|
||||||
utilz::patch_u32(&mut rom, jmp_to_so + 2, so_state_addr);
|
|
||||||
|
|
||||||
// Desenha "SO" (Quadrado Ciano)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 0); // 160
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 1); // 90
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 2); // 40
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 2); // 40
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 15); // Cyan
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut rom, 0x1002);
|
|
||||||
|
|
||||||
// Monitora Inserção de Cartucho (Hot-plug) + START
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut rom, 0x0001); // has_cart?
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 8); // Start
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut rom, 0x2001); // get_pad
|
|
||||||
utilz::emit_op(&mut rom, OpCode::And); // has_cart AND START
|
|
||||||
let jmp_so_continue = rom.len();
|
|
||||||
utilz::emit_op(&mut rom, OpCode::JmpIfFalse);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut rom, 0x0002); // run_cart
|
|
||||||
|
|
||||||
let so_continue_addr = rom.len() as u32;
|
|
||||||
utilz::patch_u32(&mut rom, jmp_so_continue + 2, so_continue_addr);
|
|
||||||
|
|
||||||
// Incrementa G0 (f)
|
|
||||||
let end_state_addr = rom.len() as u32;
|
|
||||||
utilz::patch_u32(&mut rom, jmp_end_state + 2, end_state_addr);
|
|
||||||
|
|
||||||
utilz::emit_op(&mut rom, OpCode::GetGlobal);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 7); // 1
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Add);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::SetGlobal);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
|
|
||||||
utilz::emit_op(&mut rom, OpCode::FrameSync);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Jmp);
|
|
||||||
utilz::emit_u32(&mut rom, loop_start);
|
|
||||||
|
|
||||||
Self::new(rom, constant_pool)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for VirtualMachine {
|
impl Default for VirtualMachine {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new_boot_rom()
|
Self::new(vec![], vec![])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -633,255 +396,3 @@ impl VirtualMachine {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_add_instructions() {
|
|
||||||
struct NoopNative;
|
|
||||||
impl NativeInterface for NoopNative {
|
|
||||||
fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result<u64, String> { Ok(0) }
|
|
||||||
}
|
|
||||||
let mut native = NoopNative;
|
|
||||||
|
|
||||||
let mut rom = Vec::new();
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 0); // Const index 0
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 1); // Const index 1
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Add);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Halt);
|
|
||||||
|
|
||||||
let constant_pool = vec![Value::Integer(10), Value::Integer(20)];
|
|
||||||
let mut vm = VirtualMachine::new(rom, constant_pool);
|
|
||||||
|
|
||||||
vm.run_budget(100, &mut native).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(vm.operand_stack.len(), 1);
|
|
||||||
assert_eq!(vm.operand_stack[0], Value::Integer(30));
|
|
||||||
assert!(vm.halted);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_jump_and_loop() {
|
|
||||||
struct NoopNative;
|
|
||||||
impl NativeInterface for NoopNative {
|
|
||||||
fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result<u64, String> { Ok(0) }
|
|
||||||
}
|
|
||||||
let mut native = NoopNative;
|
|
||||||
|
|
||||||
let mut rom = Vec::new();
|
|
||||||
// Index 0: PUSH 0 (counter)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
|
|
||||||
// Index 6: DUP
|
|
||||||
let loop_start = rom.len();
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Dup);
|
|
||||||
|
|
||||||
// PUSH 10
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 1);
|
|
||||||
|
|
||||||
// LT (counter < 10)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Lt);
|
|
||||||
|
|
||||||
// JMP_IF_FALSE to end
|
|
||||||
utilz::emit_op(&mut rom, OpCode::JmpIfFalse);
|
|
||||||
let jmp_placeholder = rom.len();
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
|
|
||||||
// PUSH 1
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 2);
|
|
||||||
|
|
||||||
// ADD (increment counter)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Add);
|
|
||||||
|
|
||||||
// JMP to start
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Jmp);
|
|
||||||
utilz::emit_u32(&mut rom, loop_start as u32);
|
|
||||||
|
|
||||||
// End
|
|
||||||
let loop_end = rom.len();
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Halt);
|
|
||||||
|
|
||||||
// Patch JMP_IF_FALSE addr
|
|
||||||
let end_addr_bytes = (loop_end as u32).to_le_bytes();
|
|
||||||
rom[jmp_placeholder..jmp_placeholder+4].copy_from_slice(&end_addr_bytes);
|
|
||||||
|
|
||||||
let constant_pool = vec![Value::Integer(0), Value::Integer(10), Value::Integer(1)];
|
|
||||||
let mut vm = VirtualMachine::new(rom, constant_pool);
|
|
||||||
|
|
||||||
vm.run_budget(1000, &mut native).unwrap();
|
|
||||||
|
|
||||||
assert!(vm.halted);
|
|
||||||
// O valor final na pilha deve ser 10
|
|
||||||
assert_eq!(vm.operand_stack.last(), Some(&Value::Integer(10)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_native_call_gfx_clear() {
|
|
||||||
struct MockGfx {
|
|
||||||
cleared_color: Option<usize>,
|
|
||||||
}
|
|
||||||
impl NativeInterface for MockGfx {
|
|
||||||
fn syscall(&mut self, id: u32, vm: &mut VirtualMachine) -> Result<u64, String> {
|
|
||||||
if id == 0x1001 {
|
|
||||||
let color = vm.pop_integer()? as usize;
|
|
||||||
self.cleared_color = Some(color);
|
|
||||||
return Ok(100);
|
|
||||||
}
|
|
||||||
Ok(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut native = MockGfx { cleared_color: None };
|
|
||||||
|
|
||||||
let mut rom = Vec::new();
|
|
||||||
// PUSH 5 (color index)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
// CALL_NATIVE 0x1001
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut rom, 0x1001);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Halt);
|
|
||||||
|
|
||||||
let constant_pool = vec![Value::Integer(5)];
|
|
||||||
let mut vm = VirtualMachine::new(rom, constant_pool);
|
|
||||||
|
|
||||||
vm.run_budget(1000, &mut native).unwrap();
|
|
||||||
|
|
||||||
assert!(vm.halted);
|
|
||||||
assert_eq!(native.cleared_color, Some(5));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_system_run_cart() {
|
|
||||||
use crate::LogicalHardware;
|
|
||||||
use crate::model::Cartridge;
|
|
||||||
|
|
||||||
let mut logical_hardware = LogicalHardware::new();
|
|
||||||
|
|
||||||
// 1. Verifica que não tem cartucho inicialmente
|
|
||||||
let mut rom = Vec::new();
|
|
||||||
// CALL_NATIVE 0x0001 (has_cart)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut rom, 0x0001);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Halt);
|
|
||||||
|
|
||||||
logical_hardware.vm = VirtualMachine::new(rom, vec![]);
|
|
||||||
let mut vm = std::mem::take(&mut logical_hardware.vm);
|
|
||||||
vm.run_budget(100, &mut logical_hardware).unwrap();
|
|
||||||
logical_hardware.vm = vm;
|
|
||||||
|
|
||||||
assert_eq!(logical_hardware.vm.pop().unwrap(), Value::Boolean(false));
|
|
||||||
|
|
||||||
// 2. Adiciona um cartucho e roda
|
|
||||||
let mut cart_rom = Vec::new();
|
|
||||||
// PUSH_CONST 0
|
|
||||||
utilz::emit_op(&mut cart_rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut cart_rom, 0);
|
|
||||||
utilz::emit_op(&mut cart_rom, OpCode::Halt);
|
|
||||||
let cart_pool = vec![Value::Integer(42)];
|
|
||||||
|
|
||||||
let cart = Cartridge::new(Program::new(cart_rom, cart_pool));
|
|
||||||
logical_hardware.load_cartridge(cart);
|
|
||||||
|
|
||||||
// Código para rodar o cartucho
|
|
||||||
let mut boot_rom = Vec::new();
|
|
||||||
// CALL_NATIVE 0x0002 (run_cart)
|
|
||||||
utilz::emit_op(&mut boot_rom, OpCode::Syscall);
|
|
||||||
utilz::emit_u32(&mut boot_rom, 0x0002);
|
|
||||||
|
|
||||||
logical_hardware.vm = VirtualMachine::new(boot_rom, vec![]);
|
|
||||||
let mut vm = std::mem::take(&mut logical_hardware.vm);
|
|
||||||
vm.run_budget(1000, &mut logical_hardware).unwrap();
|
|
||||||
logical_hardware.vm = vm;
|
|
||||||
|
|
||||||
// Após o run_budget, a VM deve ter executado o cartucho
|
|
||||||
assert_eq!(logical_hardware.vm.operand_stack.last(), Some(&Value::Integer(42)));
|
|
||||||
assert!(logical_hardware.vm.halted);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_stack_operations() {
|
|
||||||
struct NoopNative;
|
|
||||||
impl NativeInterface for NoopNative {
|
|
||||||
fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result<u64, String> { Ok(0) }
|
|
||||||
}
|
|
||||||
let mut native = NoopNative;
|
|
||||||
|
|
||||||
let mut rom = Vec::new();
|
|
||||||
// PUSH 10, PUSH 20, SWAP, POP
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 1);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Swap);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Pop);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Halt);
|
|
||||||
|
|
||||||
let constant_pool = vec![Value::Integer(10), Value::Integer(20)];
|
|
||||||
let mut vm = VirtualMachine::new(rom, constant_pool);
|
|
||||||
vm.run_budget(100, &mut native).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(vm.operand_stack.len(), 1);
|
|
||||||
assert_eq!(vm.operand_stack[0], Value::Integer(20));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_logical_operations() {
|
|
||||||
struct NoopNative;
|
|
||||||
impl NativeInterface for NoopNative {
|
|
||||||
fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result<u64, String> { Ok(0) }
|
|
||||||
}
|
|
||||||
let mut native = NoopNative;
|
|
||||||
|
|
||||||
let mut rom = Vec::new();
|
|
||||||
// PUSH true, NOT (-> false), PUSH true, OR (-> true)
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 0); // true
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Not);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 0); // true
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Or);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Halt);
|
|
||||||
|
|
||||||
let constant_pool = vec![Value::Boolean(true)];
|
|
||||||
let mut vm = VirtualMachine::new(rom, constant_pool);
|
|
||||||
vm.run_budget(100, &mut native).unwrap();
|
|
||||||
|
|
||||||
assert_eq!(vm.operand_stack.last(), Some(&Value::Boolean(true)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_scopes() {
|
|
||||||
struct NoopNative;
|
|
||||||
impl NativeInterface for NoopNative {
|
|
||||||
fn syscall(&mut self, _id: u32, _vm: &mut VirtualMachine) -> Result<u64, String> { Ok(0) }
|
|
||||||
}
|
|
||||||
let mut native = NoopNative;
|
|
||||||
|
|
||||||
let mut rom = Vec::new();
|
|
||||||
// PUSH_SCOPE 2 (reserves 2 nulls), PUSH 42, SET_LOCAL 0, GET_LOCAL 0, POP_SCOPE
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushScope);
|
|
||||||
utilz::emit_u32(&mut rom, 2);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PushConst);
|
|
||||||
utilz::emit_u32(&mut rom, 0); // 42
|
|
||||||
utilz::emit_op(&mut rom, OpCode::SetLocal);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::GetLocal);
|
|
||||||
utilz::emit_u32(&mut rom, 0);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::PopScope);
|
|
||||||
utilz::emit_op(&mut rom, OpCode::Halt);
|
|
||||||
|
|
||||||
let constant_pool = vec![Value::Integer(42)];
|
|
||||||
let mut vm = VirtualMachine::new(rom, constant_pool);
|
|
||||||
vm.run_budget(100, &mut native).unwrap();
|
|
||||||
|
|
||||||
// Stack should be empty because POP_SCOPE truncates to stack_base
|
|
||||||
assert_eq!(vm.operand_stack.len(), 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use crate::audio_mixer::AudioMixer;
|
|||||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||||
use pixels::{Pixels, SurfaceTexture};
|
use pixels::{Pixels, SurfaceTexture};
|
||||||
use prometeu_core::peripherals::{AudioCommand, InputSignals, OUTPUT_SAMPLE_RATE};
|
use prometeu_core::peripherals::{AudioCommand, InputSignals, OUTPUT_SAMPLE_RATE};
|
||||||
use prometeu_core::LogicalHardware;
|
use prometeu_core::Hardware;
|
||||||
use ringbuf::traits::{Consumer, Producer, Split};
|
use ringbuf::traits::{Consumer, Producer, Split};
|
||||||
use ringbuf::HeapRb;
|
use ringbuf::HeapRb;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -18,7 +18,7 @@ pub struct PrometeuRunner {
|
|||||||
window: Option<&'static Window>,
|
window: Option<&'static Window>,
|
||||||
pixels: Option<Pixels<'static>>,
|
pixels: Option<Pixels<'static>>,
|
||||||
|
|
||||||
logical_hardware: LogicalHardware,
|
hardware_bridge: Box<Hardware>,
|
||||||
|
|
||||||
input_signals: InputSignals,
|
input_signals: InputSignals,
|
||||||
|
|
||||||
@ -43,11 +43,8 @@ impl PrometeuRunner {
|
|||||||
Self {
|
Self {
|
||||||
window: None,
|
window: None,
|
||||||
pixels: None,
|
pixels: None,
|
||||||
|
hardware_bridge: Hardware::new(),
|
||||||
logical_hardware: LogicalHardware::new(),
|
|
||||||
|
|
||||||
input_signals: InputSignals::default(),
|
input_signals: InputSignals::default(),
|
||||||
|
|
||||||
frame_target_dt: Duration::from_nanos(1_000_000_000 / target_fps),
|
frame_target_dt: Duration::from_nanos(1_000_000_000 / target_fps),
|
||||||
last_frame_time: Instant::now(),
|
last_frame_time: Instant::now(),
|
||||||
accumulator: Duration::ZERO,
|
accumulator: Duration::ZERO,
|
||||||
@ -148,7 +145,7 @@ impl ApplicationHandler for PrometeuRunner {
|
|||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
let surface_texture = SurfaceTexture::new(size.width, size.height, window);
|
let surface_texture = SurfaceTexture::new(size.width, size.height, window);
|
||||||
|
|
||||||
let mut pixels = Pixels::new(LogicalHardware::W as u32, LogicalHardware::H as u32, surface_texture)
|
let mut pixels = Pixels::new(Hardware::W as u32, Hardware::H as u32, surface_texture)
|
||||||
.expect("failed to create Pixels");
|
.expect("failed to create Pixels");
|
||||||
|
|
||||||
pixels.frame_mut().fill(0);
|
pixels.frame_mut().fill(0);
|
||||||
@ -183,7 +180,7 @@ impl ApplicationHandler for PrometeuRunner {
|
|||||||
let frame = pixels.frame_mut();
|
let frame = pixels.frame_mut();
|
||||||
|
|
||||||
// Borrow imutável do core (campo diferente, ok)
|
// Borrow imutável do core (campo diferente, ok)
|
||||||
let src = self.logical_hardware.gfx.front_buffer();
|
let src = self.hardware_bridge.gfx.front_buffer();
|
||||||
|
|
||||||
draw_rgb565_to_rgba8(src, frame);
|
draw_rgb565_to_rgba8(src, frame);
|
||||||
} // <- frame borrow termina aqui
|
} // <- frame borrow termina aqui
|
||||||
@ -256,11 +253,11 @@ impl ApplicationHandler for PrometeuRunner {
|
|||||||
|
|
||||||
// 🔥 O coração do determinismo: consome o tempo em fatias exatas de 60Hz
|
// 🔥 O coração do determinismo: consome o tempo em fatias exatas de 60Hz
|
||||||
while self.accumulator >= self.frame_target_dt {
|
while self.accumulator >= self.frame_target_dt {
|
||||||
self.logical_hardware.step_frame(&self.input_signals);
|
self.hardware_bridge.step_frame(&self.input_signals);
|
||||||
|
|
||||||
// Envia comandos de áudio gerados neste frame para a thread de áudio
|
// Envia comandos de áudio gerados neste frame para a thread de áudio
|
||||||
if let Some(producer) = &mut self.audio_producer {
|
if let Some(producer) = &mut self.audio_producer {
|
||||||
for cmd in self.logical_hardware.audio.commands.drain(..) {
|
for cmd in self.hardware_bridge.audio.commands.drain(..) {
|
||||||
let _ = producer.try_push(cmd);
|
let _ = producer.try_push(cmd);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -282,11 +279,11 @@ impl ApplicationHandler for PrometeuRunner {
|
|||||||
if stats_elapsed >= Duration::from_secs(1) {
|
if stats_elapsed >= Duration::from_secs(1) {
|
||||||
if let Some(window) = self.window {
|
if let Some(window) = self.window {
|
||||||
let fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64();
|
let fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64();
|
||||||
let kb = self.logical_hardware.gfx.memory_usage_bytes() as f64 / 1024.0;
|
let kb = self.hardware_bridge.gfx.memory_usage_bytes() as f64 / 1024.0;
|
||||||
|
|
||||||
// comparação fixa sempre contra 60Hz, manter mesmo quando fazer teste de stress na CPU
|
// comparação fixa sempre contra 60Hz, manter mesmo quando fazer teste de stress na CPU
|
||||||
let frame_budget_us = 16666.0;
|
let frame_budget_us = 16666.0;
|
||||||
let cpu_load_core = (self.logical_hardware.last_frame_cpu_time_us as f64 / frame_budget_us) * 100.0;
|
let cpu_load_core = (self.hardware_bridge.last_frame_cpu_time_us() as f64 / frame_budget_us) * 100.0;
|
||||||
|
|
||||||
let cpu_load_audio = if self.audio_load_samples > 0 {
|
let cpu_load_audio = if self.audio_load_samples > 0 {
|
||||||
// O load real é (tempo total processando) / (tempo total de parede).
|
// O load real é (tempo total processando) / (tempo total de parede).
|
||||||
@ -297,7 +294,7 @@ impl ApplicationHandler for PrometeuRunner {
|
|||||||
|
|
||||||
let title = format!(
|
let title = format!(
|
||||||
"PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Load: {:.1}% (C) + {:.1}% (A) | Frame: tick {} logical {}",
|
"PROMETEU | GFX: {:.1} KB | FPS: {:.1} | Load: {:.1}% (C) + {:.1}% (A) | Frame: tick {} logical {}",
|
||||||
kb, fps, cpu_load_core, cpu_load_audio, self.logical_hardware.tick_index, self.logical_hardware.logical_frame_index
|
kb, fps, cpu_load_core, cpu_load_audio, self.hardware_bridge.tick_index(), self.hardware_bridge.logical_frame_index()
|
||||||
);
|
);
|
||||||
window.set_title(&title);
|
window.set_title(&title);
|
||||||
}
|
}
|
||||||
@ -316,13 +313,13 @@ impl ApplicationHandler for PrometeuRunner {
|
|||||||
/// Depois podemos fazer letterbox/aspect-ratio correto.
|
/// Depois podemos fazer letterbox/aspect-ratio correto.
|
||||||
fn window_to_fb(wx: f32, wy: f32, window: &Window) -> (i32, i32) {
|
fn window_to_fb(wx: f32, wy: f32, window: &Window) -> (i32, i32) {
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
let fb_w = LogicalHardware::W as f32;
|
let fb_w = Hardware::W as f32;
|
||||||
let fb_h = LogicalHardware::H as f32;
|
let fb_h = Hardware::H as f32;
|
||||||
|
|
||||||
let x = (wx * fb_w / size.width as f32).floor() as i32;
|
let x = (wx * fb_w / size.width as f32).floor() as i32;
|
||||||
let y = (wy * fb_h / size.height as f32).floor() as i32;
|
let y = (wy * fb_h / size.height as f32).floor() as i32;
|
||||||
|
|
||||||
(x.clamp(0, LogicalHardware::W as i32 - 1), y.clamp(0, LogicalHardware::H as i32 - 1))
|
(x.clamp(0, Hardware::W as i32 - 1), y.clamp(0, Hardware::H as i32 - 1))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Copia RGB565 (u16) -> RGBA8888 (u8[4]) para o frame do pixels.
|
/// Copia RGB565 (u16) -> RGBA8888 (u8[4]) para o frame do pixels.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user