add boot loader

This commit is contained in:
Nilton Constantino 2026-01-17 16:45:19 +00:00
parent 4a6def6aa0
commit 990e3ead2a
No known key found for this signature in database
10 changed files with 88 additions and 22 deletions

View File

@ -4,6 +4,6 @@
"app_id": 1234, "app_id": 1234,
"title": "Cart de Teste", "title": "Cart de Teste",
"app_version": "1.0.0", "app_version": "1.0.0",
"app_mode": "Invalid", "app_mode": "Game",
"entrypoint": "0" "entrypoint": "0"
} }

View File

@ -5,6 +5,7 @@ mod log_sink;
use crate::prometeu_runner::PrometeuRunner; use crate::prometeu_runner::PrometeuRunner;
use winit::event_loop::EventLoop; use winit::event_loop::EventLoop;
use prometeu_core::firmware::BootTarget;
use prometeu_core::telemetry::CertificationConfig; use prometeu_core::telemetry::CertificationConfig;
fn load_cap_config(path: &str) -> Option<CertificationConfig> { fn load_cap_config(path: &str) -> Option<CertificationConfig> {
@ -37,14 +38,26 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let args: Vec<String> = std::env::args().collect(); let args: Vec<String> = std::env::args().collect();
let mut fs_root = None; let mut fs_root = None;
let mut cap_config = None; let mut cap_config = None;
let mut cartridge_path = None; let mut boot_target = BootTarget::Hub;
let mut i = 1; // Pula o nome do executável let mut i = 1; // Pula o nome do executável
while i < args.len() { while i < args.len() {
match args[i].as_str() { match args[i].as_str() {
"run" => { "--run" => {
if i + 1 < args.len() { if i + 1 < args.len() {
cartridge_path = Some(args[i + 1].clone()); boot_target = BootTarget::Cartridge {
path: args[i + 1].clone(),
debug: false,
};
i += 1;
}
}
"--debug" => {
if i + 1 < args.len() {
boot_target = BootTarget::Cartridge {
path: args[i + 1].clone(),
debug: true,
};
i += 1; i += 1;
} }
} }
@ -68,16 +81,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
let event_loop = EventLoop::new()?; let event_loop = EventLoop::new()?;
let mut runner = PrometeuRunner::new(fs_root, cap_config); let mut runner = PrometeuRunner::new(fs_root, cap_config);
runner.set_boot_target(boot_target);
if let Some(path) = cartridge_path {
match runner.load_cartridge(&path) {
Ok(_) => println!("Cartridge loaded: {}", path),
Err(e) => {
eprintln!("Failed to load cartridge: {:?}", e);
return Ok(());
}
}
}
event_loop.run_app(&mut runner)?; event_loop.run_app(&mut runner)?;

View File

@ -3,7 +3,7 @@ use crate::fs_desktop_backend::HostDirBackend;
use crate::log_sink::HostConsoleSink; use crate::log_sink::HostConsoleSink;
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait}; use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
use pixels::{Pixels, SurfaceTexture}; use pixels::{Pixels, SurfaceTexture};
use prometeu_core::firmware::Firmware; use prometeu_core::firmware::{BootTarget, Firmware};
use prometeu_core::hardware::{AudioCommand, InputSignals, OUTPUT_SAMPLE_RATE}; use prometeu_core::hardware::{AudioCommand, InputSignals, OUTPUT_SAMPLE_RATE};
use prometeu_core::Hardware; use prometeu_core::Hardware;
use ringbuf::traits::{Consumer, Producer, Split}; use ringbuf::traits::{Consumer, Producer, Split};
@ -42,6 +42,8 @@ pub struct PrometeuRunner {
overlay_enabled: bool, overlay_enabled: bool,
debug_waiting_for_start: bool,
audio_load_accum_us: u64, audio_load_accum_us: u64,
audio_load_samples: u64, audio_load_samples: u64,
audio_perf_consumer: Option<ringbuf::wrap::CachingCons<Arc<HeapRb<u64>>>>, audio_perf_consumer: Option<ringbuf::wrap::CachingCons<Arc<HeapRb<u64>>>>,
@ -51,6 +53,15 @@ pub struct PrometeuRunner {
} }
impl PrometeuRunner { impl PrometeuRunner {
pub(crate) fn set_boot_target(&mut self, boot_target: BootTarget) {
self.firmware.boot_target = boot_target.clone();
if let BootTarget::Cartridge { debug: true, .. } = boot_target {
self.debug_waiting_for_start = true;
println!("[Debugger] Waiting for start command...");
println!("[Debugger] (Stub: press D to start execution)");
}
}
pub(crate) fn load_cartridge(&mut self, path: &str) -> Result<(), CartridgeError> { pub(crate) fn load_cartridge(&mut self, path: &str) -> Result<(), CartridgeError> {
let cartridge = CartridgeLoader::load(path)?; let cartridge = CartridgeLoader::load(path)?;
self.firmware.load_cartridge(cartridge); self.firmware.load_cartridge(cartridge);
@ -82,6 +93,7 @@ impl PrometeuRunner {
frames_since_last_update: 0, frames_since_last_update: 0,
current_fps: 0.0, current_fps: 0.0,
overlay_enabled: false, overlay_enabled: false,
debug_waiting_for_start: false,
audio_load_accum_us: 0, audio_load_accum_us: 0,
audio_load_samples: 0, audio_load_samples: 0,
audio_perf_consumer: None, audio_perf_consumer: None,
@ -225,6 +237,12 @@ impl ApplicationHandler for PrometeuRunner {
WindowEvent::KeyboardInput { event, .. } => { WindowEvent::KeyboardInput { event, .. } => {
if let PhysicalKey::Code(code) = event.physical_key { if let PhysicalKey::Code(code) = event.physical_key {
let is_down = event.state == ElementState::Pressed; let is_down = event.state == ElementState::Pressed;
if is_down && code == KeyCode::KeyD && self.debug_waiting_for_start {
self.debug_waiting_for_start = false;
println!("[Debugger] Execution started!");
}
match code { match code {
KeyCode::ArrowUp => self.input_signals.up_signal = is_down, KeyCode::ArrowUp => self.input_signals.up_signal = is_down,
KeyCode::ArrowDown => self.input_signals.down_signal = is_down, KeyCode::ArrowDown => self.input_signals.down_signal = is_down,
@ -301,7 +319,9 @@ 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 {
if !self.debug_waiting_for_start {
self.firmware.step_frame(&self.input_signals, &mut self.hardware); self.firmware.step_frame(&self.input_signals, &mut self.hardware);
}
// 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 {

View File

@ -0,0 +1,11 @@
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum BootTarget {
Hub,
Cartridge { path: String, debug: bool },
}
impl Default for BootTarget {
fn default() -> Self {
Self::Hub
}
}

View File

@ -1,3 +1,4 @@
use crate::firmware::boot_target::BootTarget;
use crate::firmware::firmware_state::{FirmwareState, LoadCartridgeStep, ResetStep}; use crate::firmware::firmware_state::{FirmwareState, LoadCartridgeStep, ResetStep};
use crate::firmware::prometeu_context::PrometeuContext; use crate::firmware::prometeu_context::PrometeuContext;
use crate::hardware::{HardwareBridge, InputSignals}; use crate::hardware::{HardwareBridge, InputSignals};
@ -13,6 +14,7 @@ pub struct Firmware {
pub os: PrometeuOS, pub os: PrometeuOS,
pub hub: PrometeuHub, pub hub: PrometeuHub,
pub state: FirmwareState, pub state: FirmwareState,
pub boot_target: BootTarget,
state_initialized: bool, state_initialized: bool,
} }
@ -23,6 +25,7 @@ impl Firmware {
os: PrometeuOS::new(cap_config), os: PrometeuOS::new(cap_config),
hub: PrometeuHub::new(), hub: PrometeuHub::new(),
state: FirmwareState::Reset(ResetStep), state: FirmwareState::Reset(ResetStep),
boot_target: BootTarget::Hub,
state_initialized: false, state_initialized: false,
} }
} }
@ -57,6 +60,7 @@ impl Firmware {
vm: &mut self.vm, vm: &mut self.vm,
os: &mut self.os, os: &mut self.os,
hub: &mut self.hub, hub: &mut self.hub,
boot_target: &self.boot_target,
signals, signals,
hw, hw,
}; };
@ -76,6 +80,7 @@ impl Firmware {
vm: &mut self.vm, vm: &mut self.vm,
os: &mut self.os, os: &mut self.os,
hub: &mut self.hub, hub: &mut self.hub,
boot_target: &self.boot_target,
signals, signals,
hw, hw,
}; };
@ -95,6 +100,7 @@ impl Firmware {
vm: &mut self.vm, vm: &mut self.vm,
os: &mut self.os, os: &mut self.os,
hub: &mut self.hub, hub: &mut self.hub,
boot_target: &self.boot_target,
signals, signals,
hw, hw,
}; };

View File

@ -1,5 +1,7 @@
use crate::firmware::firmware_state::{FirmwareState, HubHomeStep}; use crate::firmware::boot_target::BootTarget;
use crate::firmware::firmware_state::{FirmwareState, HubHomeStep, LoadCartridgeStep};
use crate::firmware::prometeu_context::PrometeuContext; use crate::firmware::prometeu_context::PrometeuContext;
use crate::model::CartridgeLoader;
use crate::log::{LogLevel, LogSource}; use crate::log::{LogLevel, LogSource};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -11,7 +13,19 @@ impl LaunchHubStep {
ctx.hub.init(); ctx.hub.init();
} }
pub fn on_update(&mut self, _ctx: &mut PrometeuContext) -> Option<FirmwareState> { pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
if let BootTarget::Cartridge { path, debug } = ctx.boot_target {
match CartridgeLoader::load(path) {
Ok(cartridge) => {
// No caso de debug, aqui poderíamos pausar, mas o requisito diz
// para o Runtime abrir o socket e aguardar.
return Some(FirmwareState::LoadCartridge(LoadCartridgeStep { cartridge }));
}
Err(e) => {
ctx.os.log(LogLevel::Error, LogSource::Pos, 0, format!("Failed to auto-load cartridge: {:?}", e));
}
}
}
Some(FirmwareState::HubHome(HubHomeStep)) Some(FirmwareState::HubHome(HubHomeStep))
} }

View File

@ -22,6 +22,9 @@ impl LoadCartridgeStep {
Color::WHITE Color::WHITE
); );
ctx.hub.window_manager.set_focus(id); ctx.hub.window_manager.set_focus(id);
// Apps de sistema não mudam o estado do firmware para GameRunning.
// Eles rodam em background ou via janelas no Hub.
return Some(FirmwareState::HubHome(HubHomeStep)); return Some(FirmwareState::HubHome(HubHomeStep));
} }

View File

@ -1,4 +1,5 @@
use crate::firmware::firmware_state::{FirmwareState, SplashScreenStep}; use crate::firmware::boot_target::BootTarget;
use crate::firmware::firmware_state::{FirmwareState, LaunchHubStep, SplashScreenStep};
use crate::firmware::prometeu_context::PrometeuContext; use crate::firmware::prometeu_context::PrometeuContext;
use crate::log::{LogLevel, LogSource}; use crate::log::{LogLevel, LogSource};
@ -11,8 +12,11 @@ impl ResetStep {
ctx.os.reset(ctx.vm); ctx.os.reset(ctx.vm);
} }
pub fn on_update(&mut self, _ctx: &mut PrometeuContext) -> Option<FirmwareState> { pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
Some(FirmwareState::SplashScreen(SplashScreenStep { frame: 0 })) match ctx.boot_target {
BootTarget::Hub => Some(FirmwareState::SplashScreen(SplashScreenStep { frame: 0 })),
BootTarget::Cartridge { .. } => Some(FirmwareState::LaunchHub(LaunchHubStep)),
}
} }
pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {} pub fn on_exit(&mut self, _ctx: &mut PrometeuContext) {}

View File

@ -1,5 +1,6 @@
mod firmware; mod firmware;
pub mod firmware_state; pub mod firmware_state;
mod boot_target;
pub(crate) mod firmware_step_reset; pub(crate) mod firmware_step_reset;
pub(crate) mod firmware_step_splash_screen; pub(crate) mod firmware_step_splash_screen;
@ -13,3 +14,4 @@ mod prometeu_context;
pub use firmware::Firmware; pub use firmware::Firmware;
pub use firmware_state::FirmwareState; pub use firmware_state::FirmwareState;
pub use prometeu_context::PrometeuContext; pub use prometeu_context::PrometeuContext;
pub use boot_target::BootTarget;

View File

@ -1,3 +1,4 @@
use crate::firmware::boot_target::BootTarget;
use crate::hardware::{HardwareBridge, InputSignals}; use crate::hardware::{HardwareBridge, InputSignals};
use crate::prometeu_hub::PrometeuHub; use crate::prometeu_hub::PrometeuHub;
use crate::prometeu_os::PrometeuOS; use crate::prometeu_os::PrometeuOS;
@ -7,6 +8,7 @@ pub struct PrometeuContext<'a> {
pub vm: &'a mut VirtualMachine, pub vm: &'a mut VirtualMachine,
pub os: &'a mut PrometeuOS, pub os: &'a mut PrometeuOS,
pub hub: &'a mut PrometeuHub, pub hub: &'a mut PrometeuHub,
pub boot_target: &'a BootTarget,
pub signals: &'a InputSignals, pub signals: &'a InputSignals,
pub hw: &'a mut dyn HardwareBridge, pub hw: &'a mut dyn HardwareBridge,
} }