Compare commits
10 Commits
a268bd0b75
...
9cf9fa6163
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cf9fa6163 | |||
| 117a18ee72 | |||
| f45b3a38cb | |||
| 5381ae02b1 | |||
| 1d6b799960 | |||
| d0a2682366 | |||
| c62aef51ba | |||
| d2bace2612 | |||
| 6f3df74e79 | |||
| 18cfeedbff |
@ -8,8 +8,8 @@ use prometeu_hal::scene_viewport_resolver::{
|
||||
};
|
||||
use prometeu_hal::sprite::Sprite;
|
||||
use prometeu_hal::{
|
||||
BoundScenePacket, Camera2D, ComposerFramePacket, Game2DFramePacket, GameSpritePacket,
|
||||
GfxBridge, HudPacket,
|
||||
BoundScenePacket, Camera2D, ComposerFramePacket, ComposerOpStatus, Game2DFrameComposer,
|
||||
Game2DFramePacket, GameSpritePacket, GfxBridge, HudPacket,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -452,6 +452,41 @@ impl FrameComposer {
|
||||
}
|
||||
}
|
||||
|
||||
impl Game2DFrameComposer for FrameComposer {
|
||||
fn begin_frame(&mut self) {
|
||||
FrameComposer::begin_frame(self);
|
||||
}
|
||||
|
||||
fn bind_scene(&mut self, scene_bank_id: usize) -> ComposerOpStatus {
|
||||
if FrameComposer::bind_scene(self, scene_bank_id) {
|
||||
ComposerOpStatus::Ok
|
||||
} else {
|
||||
ComposerOpStatus::SceneUnavailable
|
||||
}
|
||||
}
|
||||
|
||||
fn unbind_scene(&mut self) {
|
||||
FrameComposer::unbind_scene(self);
|
||||
}
|
||||
|
||||
fn set_camera(&mut self, x: i32, y: i32) -> ComposerOpStatus {
|
||||
FrameComposer::set_camera(self, x, y);
|
||||
ComposerOpStatus::Ok
|
||||
}
|
||||
|
||||
fn emit_sprite(&mut self, sprite: Sprite) -> ComposerOpStatus {
|
||||
if FrameComposer::emit_sprite(self, sprite) {
|
||||
ComposerOpStatus::Ok
|
||||
} else {
|
||||
ComposerOpStatus::SpriteOverflow
|
||||
}
|
||||
}
|
||||
|
||||
fn close_game2d_packet(&self) -> Game2DFramePacket {
|
||||
FrameComposer::close_game2d_packet(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@ -896,6 +931,45 @@ mod tests {
|
||||
assert!(packet.gfx2d.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn game2d_frame_composer_facade_closes_equivalent_packet() {
|
||||
let mut frame_composer = FrameComposer::new(
|
||||
Hardware::W,
|
||||
Hardware::H,
|
||||
Arc::new(MemoryBanks::default()),
|
||||
make_glyph_slot_index(&[]),
|
||||
);
|
||||
|
||||
let composer: &mut dyn Game2DFrameComposer = &mut frame_composer;
|
||||
assert_eq!(composer.set_camera(10, 20), ComposerOpStatus::Ok);
|
||||
composer.begin_frame();
|
||||
assert_eq!(
|
||||
composer.emit_sprite(Sprite {
|
||||
glyph: Glyph { glyph_id: 7, palette_id: 3 },
|
||||
x: 4,
|
||||
y: 5,
|
||||
layer: 2,
|
||||
bank_id: 1,
|
||||
active: false,
|
||||
flip_x: true,
|
||||
flip_y: false,
|
||||
priority: 9,
|
||||
}),
|
||||
ComposerOpStatus::Ok
|
||||
);
|
||||
|
||||
let packet = composer.close_game2d_packet();
|
||||
|
||||
assert_eq!(packet.composer.bound_scene, None);
|
||||
assert_eq!(packet.composer.camera, Camera2D { x: 10, y: 20 });
|
||||
assert_eq!(packet.composer.sprites.len(), 1);
|
||||
assert_eq!(packet.composer.sprites[0].glyph_id, 7);
|
||||
assert_eq!(packet.composer.sprites[0].palette_id, 3);
|
||||
assert_eq!(packet.composer.sprites[0].layer, 2);
|
||||
assert!(packet.composer.sprites[0].flip_x);
|
||||
assert!(packet.gfx2d.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composer_buffer_preserves_integer_layer_ordering() {
|
||||
let mut frame_composer = FrameComposer::new(
|
||||
|
||||
@ -10,8 +10,13 @@ use crate::pad::Pad;
|
||||
use crate::touch::Touch;
|
||||
use prometeu_hal::cartridge::AssetsPayloadSource;
|
||||
use prometeu_hal::sprite::Sprite;
|
||||
use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge};
|
||||
use prometeu_hal::{Game2DFramePacket, RenderSubmission, RenderSubmissionPacket};
|
||||
use prometeu_hal::{
|
||||
AssetBridge, AudioBridge, InputPlatform, NoopTelemetryPlatform, PadBridge, RenderBackend,
|
||||
RuntimePlatform, TelemetryPlatform, TouchBridge,
|
||||
};
|
||||
use prometeu_hal::{
|
||||
Game2DFrameComposer, Game2DFramePacket, RenderSubmission, RenderSubmissionPacket,
|
||||
};
|
||||
use prometeu_hal::{RenderSubmissionSink, RenderSubmitError};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -48,32 +53,8 @@ impl Default for Hardware {
|
||||
}
|
||||
}
|
||||
|
||||
impl HardwareBridge for Hardware {
|
||||
fn begin_frame(&mut self) {
|
||||
self.frame_composer.begin_frame();
|
||||
}
|
||||
|
||||
fn bind_scene(&mut self, scene_bank_id: usize) -> bool {
|
||||
self.frame_composer.bind_scene(scene_bank_id)
|
||||
}
|
||||
|
||||
fn unbind_scene(&mut self) {
|
||||
self.frame_composer.unbind_scene();
|
||||
}
|
||||
|
||||
fn set_camera(&mut self, x: i32, y: i32) {
|
||||
self.frame_composer.set_camera(x, y);
|
||||
}
|
||||
|
||||
fn emit_sprite(&mut self, sprite: Sprite) -> bool {
|
||||
self.frame_composer.emit_sprite(sprite)
|
||||
}
|
||||
|
||||
fn close_game2d_packet(&self) -> Game2DFramePacket {
|
||||
self.frame_composer.close_game2d_packet()
|
||||
}
|
||||
|
||||
fn publish_render_submission(&mut self, submission: &RenderSubmission) {
|
||||
impl Hardware {
|
||||
fn consume_render_submission(&mut self, submission: &RenderSubmission) {
|
||||
match &submission.packet {
|
||||
RenderSubmissionPacket::Game2D(packet) => {
|
||||
if packet.composer.bound_scene.is_none() {
|
||||
@ -89,7 +70,19 @@ impl HardwareBridge for Hardware {
|
||||
}
|
||||
self.gfx.present();
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderSubmissionSink for Hardware {
|
||||
fn submit_render_submission(
|
||||
&mut self,
|
||||
submission: RenderSubmission,
|
||||
) -> Result<(), RenderSubmitError> {
|
||||
self.consume_render_submission(&submission);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBackend for Hardware {
|
||||
fn render_frame(&mut self) {
|
||||
if self.frame_composer.active_scene_id().is_none() {
|
||||
let packet = self.frame_composer.close_game2d_packet();
|
||||
@ -98,28 +91,52 @@ impl HardwareBridge for Hardware {
|
||||
self.frame_composer.render_frame(&mut self.gfx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn has_glyph_bank(&self, bank_id: usize) -> bool {
|
||||
self.gfx.glyph_banks.glyph_bank_slot(bank_id).is_some()
|
||||
impl Game2DFrameComposer for Hardware {
|
||||
fn begin_frame(&mut self) {
|
||||
self.frame_composer.begin_frame();
|
||||
}
|
||||
|
||||
fn gfx(&self) -> &dyn GfxBridge {
|
||||
&self.gfx
|
||||
}
|
||||
fn gfx_mut(&mut self) -> &mut dyn GfxBridge {
|
||||
&mut self.gfx
|
||||
fn bind_scene(&mut self, scene_bank_id: usize) -> prometeu_hal::ComposerOpStatus {
|
||||
if self.frame_composer.bind_scene(scene_bank_id) {
|
||||
prometeu_hal::ComposerOpStatus::Ok
|
||||
} else {
|
||||
prometeu_hal::ComposerOpStatus::SceneUnavailable
|
||||
}
|
||||
}
|
||||
|
||||
fn audio(&self) -> &dyn AudioBridge {
|
||||
&self.audio
|
||||
}
|
||||
fn audio_mut(&mut self) -> &mut dyn AudioBridge {
|
||||
&mut self.audio
|
||||
fn unbind_scene(&mut self) {
|
||||
self.frame_composer.unbind_scene();
|
||||
}
|
||||
|
||||
fn set_camera(&mut self, x: i32, y: i32) -> prometeu_hal::ComposerOpStatus {
|
||||
self.frame_composer.set_camera(x, y);
|
||||
prometeu_hal::ComposerOpStatus::Ok
|
||||
}
|
||||
|
||||
fn emit_sprite(&mut self, sprite: Sprite) -> prometeu_hal::ComposerOpStatus {
|
||||
if self.gfx.glyph_banks.glyph_bank_slot(sprite.bank_id as usize).is_none() {
|
||||
return prometeu_hal::ComposerOpStatus::BankInvalid;
|
||||
}
|
||||
|
||||
if self.frame_composer.emit_sprite(sprite) {
|
||||
prometeu_hal::ComposerOpStatus::Ok
|
||||
} else {
|
||||
prometeu_hal::ComposerOpStatus::SpriteOverflow
|
||||
}
|
||||
}
|
||||
|
||||
fn close_game2d_packet(&self) -> Game2DFramePacket {
|
||||
self.frame_composer.close_game2d_packet()
|
||||
}
|
||||
}
|
||||
|
||||
impl InputPlatform for Hardware {
|
||||
fn pad(&self) -> &dyn PadBridge {
|
||||
&self.pad
|
||||
}
|
||||
|
||||
fn pad_mut(&mut self) -> &mut dyn PadBridge {
|
||||
&mut self.pad
|
||||
}
|
||||
@ -127,25 +144,53 @@ impl HardwareBridge for Hardware {
|
||||
fn touch(&self) -> &dyn TouchBridge {
|
||||
&self.touch
|
||||
}
|
||||
|
||||
fn touch_mut(&mut self) -> &mut dyn TouchBridge {
|
||||
&mut self.touch
|
||||
}
|
||||
}
|
||||
|
||||
static NOOP_TELEMETRY: NoopTelemetryPlatform = NoopTelemetryPlatform;
|
||||
|
||||
impl RuntimePlatform for Hardware {
|
||||
fn render_submission_sink(&mut self) -> &mut dyn RenderSubmissionSink {
|
||||
self
|
||||
}
|
||||
|
||||
fn render_backend(&mut self) -> &mut dyn RenderBackend {
|
||||
self
|
||||
}
|
||||
|
||||
fn game2d_frame_composer(&mut self) -> &mut dyn Game2DFrameComposer {
|
||||
self
|
||||
}
|
||||
|
||||
fn audio(&self) -> &dyn AudioBridge {
|
||||
&self.audio
|
||||
}
|
||||
|
||||
fn audio_mut(&mut self) -> &mut dyn AudioBridge {
|
||||
&mut self.audio
|
||||
}
|
||||
|
||||
fn input(&self) -> &dyn InputPlatform {
|
||||
self
|
||||
}
|
||||
|
||||
fn input_mut(&mut self) -> &mut dyn InputPlatform {
|
||||
self
|
||||
}
|
||||
|
||||
fn assets(&self) -> &dyn AssetBridge {
|
||||
&self.assets
|
||||
}
|
||||
|
||||
fn assets_mut(&mut self) -> &mut dyn AssetBridge {
|
||||
&mut self.assets
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderSubmissionSink for Hardware {
|
||||
fn submit_render_submission(
|
||||
&mut self,
|
||||
submission: RenderSubmission,
|
||||
) -> Result<(), RenderSubmitError> {
|
||||
self.publish_render_submission(&submission);
|
||||
Ok(())
|
||||
fn telemetry(&self) -> &dyn TelemetryPlatform {
|
||||
&NOOP_TELEMETRY
|
||||
}
|
||||
}
|
||||
|
||||
@ -294,15 +339,22 @@ mod tests {
|
||||
let mut glyph_slots = [None; 16];
|
||||
glyph_slots[0] = Some(0);
|
||||
hardware.assets.glyph_asset_slot_index().rebuild_from_slots(&glyph_slots);
|
||||
assert!(hardware.bind_scene(0));
|
||||
assert_eq!(
|
||||
Game2DFrameComposer::bind_scene(&mut hardware, 0),
|
||||
prometeu_hal::ComposerOpStatus::Ok
|
||||
);
|
||||
|
||||
let mut packet = hardware.close_game2d_packet();
|
||||
let mut packet = Game2DFrameComposer::close_game2d_packet(&hardware);
|
||||
packet.gfx2d = vec![Gfx2dCommand::FillRect {
|
||||
rect: Rect { x: 0, y: 0, w: 1, h: 1 },
|
||||
color: Color::BLUE,
|
||||
}];
|
||||
|
||||
hardware.publish_render_submission(&RenderSubmission::game2d(FrameId::ZERO, packet));
|
||||
RenderSubmissionSink::submit_render_submission(
|
||||
&mut hardware,
|
||||
RenderSubmission::game2d(FrameId::ZERO, packet),
|
||||
)
|
||||
.expect("local sink should publish Game2D packet");
|
||||
|
||||
assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw());
|
||||
}
|
||||
@ -317,7 +369,10 @@ mod tests {
|
||||
let mut glyph_slots = [None; 16];
|
||||
glyph_slots[0] = Some(0);
|
||||
hardware.assets.glyph_asset_slot_index().rebuild_from_slots(&glyph_slots);
|
||||
assert!(hardware.bind_scene(0));
|
||||
assert_eq!(
|
||||
Game2DFrameComposer::bind_scene(&mut hardware, 0),
|
||||
prometeu_hal::ComposerOpStatus::Ok
|
||||
);
|
||||
|
||||
let packet = Game2DFramePacket::new(
|
||||
ComposerFramePacket {
|
||||
@ -339,8 +394,12 @@ mod tests {
|
||||
Vec::new(),
|
||||
);
|
||||
|
||||
hardware.begin_frame();
|
||||
hardware.publish_render_submission(&RenderSubmission::game2d(FrameId::ZERO, packet));
|
||||
Game2DFrameComposer::begin_frame(&mut hardware);
|
||||
RenderSubmissionSink::submit_render_submission(
|
||||
&mut hardware,
|
||||
RenderSubmission::game2d(FrameId::ZERO, packet),
|
||||
)
|
||||
.expect("local sink should publish Game2D packet");
|
||||
|
||||
assert_eq!(hardware.gfx.front_buffer()[0], Color::RED.raw());
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ mod gfx;
|
||||
pub mod hardware;
|
||||
mod memory_banks;
|
||||
mod pad;
|
||||
mod test_platform;
|
||||
mod touch;
|
||||
|
||||
pub use crate::asset::AssetManager;
|
||||
@ -16,3 +17,4 @@ pub use crate::memory_banks::{
|
||||
SceneBankPoolAccess, SceneBankPoolInstaller, SoundBankPoolAccess, SoundBankPoolInstaller,
|
||||
};
|
||||
pub use crate::pad::Pad;
|
||||
pub use crate::test_platform::TestPlatform;
|
||||
|
||||
100
crates/console/prometeu-drivers/src/test_platform.rs
Normal file
100
crates/console/prometeu-drivers/src/test_platform.rs
Normal file
@ -0,0 +1,100 @@
|
||||
use crate::hardware::Hardware;
|
||||
use crate::memory_banks::MemoryBanks;
|
||||
use prometeu_hal::{
|
||||
AssetBridge, AudioBridge, Game2DFrameComposer, InputPlatform, RenderBackend,
|
||||
RenderSubmissionSink, RuntimePlatform, TelemetryPlatform,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct TestPlatform {
|
||||
hardware: Hardware,
|
||||
}
|
||||
|
||||
impl Default for TestPlatform {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl TestPlatform {
|
||||
pub fn new() -> Self {
|
||||
Self { hardware: Hardware::new() }
|
||||
}
|
||||
|
||||
pub fn new_with_memory_banks(memory_banks: Arc<MemoryBanks>) -> Self {
|
||||
Self { hardware: Hardware::new_with_memory_banks(memory_banks) }
|
||||
}
|
||||
|
||||
pub fn local_hardware(&self) -> &Hardware {
|
||||
&self.hardware
|
||||
}
|
||||
|
||||
pub fn local_hardware_mut(&mut self) -> &mut Hardware {
|
||||
&mut self.hardware
|
||||
}
|
||||
}
|
||||
|
||||
impl RuntimePlatform for TestPlatform {
|
||||
fn render_submission_sink(&mut self) -> &mut dyn RenderSubmissionSink {
|
||||
self.hardware.render_submission_sink()
|
||||
}
|
||||
|
||||
fn render_backend(&mut self) -> &mut dyn RenderBackend {
|
||||
self.hardware.render_backend()
|
||||
}
|
||||
|
||||
fn game2d_frame_composer(&mut self) -> &mut dyn Game2DFrameComposer {
|
||||
self.hardware.game2d_frame_composer()
|
||||
}
|
||||
|
||||
fn audio(&self) -> &dyn AudioBridge {
|
||||
self.hardware.audio()
|
||||
}
|
||||
|
||||
fn audio_mut(&mut self) -> &mut dyn AudioBridge {
|
||||
self.hardware.audio_mut()
|
||||
}
|
||||
|
||||
fn input(&self) -> &dyn InputPlatform {
|
||||
self.hardware.input()
|
||||
}
|
||||
|
||||
fn input_mut(&mut self) -> &mut dyn InputPlatform {
|
||||
self.hardware.input_mut()
|
||||
}
|
||||
|
||||
fn assets(&self) -> &dyn AssetBridge {
|
||||
self.hardware.assets()
|
||||
}
|
||||
|
||||
fn assets_mut(&mut self) -> &mut dyn AssetBridge {
|
||||
self.hardware.assets_mut()
|
||||
}
|
||||
|
||||
fn telemetry(&self) -> &dyn TelemetryPlatform {
|
||||
self.hardware.telemetry()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use prometeu_hal::{FrameId, Game2DFramePacket, RenderSubmission};
|
||||
|
||||
#[test]
|
||||
fn test_platform_initializes_required_facades() {
|
||||
let mut platform = TestPlatform::new();
|
||||
|
||||
platform.game2d_frame_composer().begin_frame();
|
||||
platform
|
||||
.render_submission_sink()
|
||||
.submit_render_submission(RenderSubmission::game2d(
|
||||
FrameId::ZERO,
|
||||
Game2DFramePacket::default(),
|
||||
))
|
||||
.expect("test platform render sink should accept local submission");
|
||||
assert!(!platform.audio().is_playing(0));
|
||||
assert!(!platform.input().pad().any());
|
||||
assert!(platform.telemetry().telemetry_snapshot().is_none());
|
||||
}
|
||||
}
|
||||
@ -3,7 +3,7 @@ use crate::firmware::firmware_state::{FirmwareState, LoadCartridgeStep, ResetSte
|
||||
use crate::firmware::prometeu_context::PrometeuContext;
|
||||
use prometeu_hal::cartridge::Cartridge;
|
||||
use prometeu_hal::telemetry::CertificationConfig;
|
||||
use prometeu_hal::{HardwareBridge, InputSignals};
|
||||
use prometeu_hal::{InputSignals, RuntimePlatform};
|
||||
use prometeu_system::{PrometeuHub, SystemOS};
|
||||
use prometeu_vm::VirtualMachine;
|
||||
|
||||
@ -54,24 +54,24 @@ impl Firmware {
|
||||
///
|
||||
/// This method is called exactly once per Host frame (60Hz).
|
||||
/// It updates peripheral signals and delegates the logic to the current state.
|
||||
pub fn tick(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) {
|
||||
pub fn tick(&mut self, signals: &InputSignals, platform: &mut dyn RuntimePlatform) {
|
||||
// 0. Process asset commits at the beginning of the frame boundary.
|
||||
hw.assets_mut().apply_commits();
|
||||
platform.assets_mut().apply_commits();
|
||||
|
||||
// 1. Update the peripheral state using the latest signals from the Host.
|
||||
// This ensures input is consistent throughout the entire update.
|
||||
hw.pad_mut().begin_frame(signals);
|
||||
hw.touch_mut().begin_frame(signals);
|
||||
platform.input_mut().pad_mut().begin_frame(signals);
|
||||
platform.input_mut().touch_mut().begin_frame(signals);
|
||||
|
||||
// 2. State machine lifecycle management.
|
||||
if !self.state_initialized {
|
||||
self.on_enter(signals, hw);
|
||||
self.on_enter(signals, platform);
|
||||
self.state_initialized = true;
|
||||
}
|
||||
|
||||
// 3. Update the current state and check for transitions.
|
||||
if let Some(next_state) = self.on_update(signals, hw) {
|
||||
self.change_state(next_state, signals, hw);
|
||||
if let Some(next_state) = self.on_update(signals, platform) {
|
||||
self.change_state(next_state, signals, platform);
|
||||
}
|
||||
}
|
||||
|
||||
@ -80,26 +80,26 @@ impl Firmware {
|
||||
&mut self,
|
||||
new_state: FirmwareState,
|
||||
signals: &InputSignals,
|
||||
hw: &mut dyn HardwareBridge,
|
||||
platform: &mut dyn RuntimePlatform,
|
||||
) {
|
||||
self.on_exit(signals, hw);
|
||||
self.on_exit(signals, platform);
|
||||
self.state = new_state;
|
||||
self.state_initialized = false;
|
||||
|
||||
// Enter the new state immediately to avoid "empty" frames during transitions.
|
||||
self.on_enter(signals, hw);
|
||||
self.on_enter(signals, platform);
|
||||
self.state_initialized = true;
|
||||
}
|
||||
|
||||
/// Dispatches the `on_enter` event to the current state implementation.
|
||||
fn on_enter(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) {
|
||||
fn on_enter(&mut self, signals: &InputSignals, platform: &mut dyn RuntimePlatform) {
|
||||
let mut req = PrometeuContext {
|
||||
vm: &mut self.vm,
|
||||
os: &mut self.os,
|
||||
hub: &mut self.hub,
|
||||
boot_target: &self.boot_target,
|
||||
signals,
|
||||
hw,
|
||||
platform,
|
||||
};
|
||||
match &mut self.state {
|
||||
FirmwareState::Reset(s) => s.on_enter(&mut req),
|
||||
@ -118,7 +118,7 @@ impl Firmware {
|
||||
fn on_update(
|
||||
&mut self,
|
||||
signals: &InputSignals,
|
||||
hw: &mut dyn HardwareBridge,
|
||||
platform: &mut dyn RuntimePlatform,
|
||||
) -> Option<FirmwareState> {
|
||||
let mut req = PrometeuContext {
|
||||
vm: &mut self.vm,
|
||||
@ -126,7 +126,7 @@ impl Firmware {
|
||||
hub: &mut self.hub,
|
||||
boot_target: &self.boot_target,
|
||||
signals,
|
||||
hw,
|
||||
platform,
|
||||
};
|
||||
match &mut self.state {
|
||||
FirmwareState::Reset(s) => s.on_update(&mut req),
|
||||
@ -141,14 +141,14 @@ impl Firmware {
|
||||
}
|
||||
|
||||
/// Dispatches the `on_exit` event to the current state implementation.
|
||||
fn on_exit(&mut self, signals: &InputSignals, hw: &mut dyn HardwareBridge) {
|
||||
fn on_exit(&mut self, signals: &InputSignals, platform: &mut dyn RuntimePlatform) {
|
||||
let mut req = PrometeuContext {
|
||||
vm: &mut self.vm,
|
||||
os: &mut self.os,
|
||||
hub: &mut self.hub,
|
||||
boot_target: &self.boot_target,
|
||||
signals,
|
||||
hw,
|
||||
platform,
|
||||
};
|
||||
match &mut self.state {
|
||||
FirmwareState::Reset(s) => s.on_exit(&mut req),
|
||||
@ -174,7 +174,7 @@ mod tests {
|
||||
use crate::firmware::firmware_state::HubHomeStep;
|
||||
use prometeu_bytecode::assembler::assemble;
|
||||
use prometeu_bytecode::model::{BytecodeModule, FunctionMeta, SyscallDecl};
|
||||
use prometeu_drivers::hardware::Hardware;
|
||||
use prometeu_drivers::TestPlatform;
|
||||
use prometeu_hal::app_mode::AppMode;
|
||||
use prometeu_hal::cartridge::AssetsPayloadSource;
|
||||
use prometeu_hal::color::Color;
|
||||
@ -267,28 +267,28 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn load_shell_running_firmware() -> (Firmware, Hardware, InputSignals, TaskId) {
|
||||
fn load_shell_running_firmware() -> (Firmware, TestPlatform, InputSignals, TaskId) {
|
||||
let mut firmware = Firmware::new(None);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
|
||||
firmware.load_cartridge(valid_cartridge(AppMode::Shell));
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
let task_id = match &firmware.state {
|
||||
FirmwareState::ShellRunning(step) => step.task_id,
|
||||
other => panic!("expected ShellRunning state, got {:?}", other),
|
||||
};
|
||||
|
||||
(firmware, hardware, signals, task_id)
|
||||
(firmware, platform, signals, task_id)
|
||||
}
|
||||
|
||||
fn hub_home_firmware() -> (Firmware, Hardware) {
|
||||
fn hub_home_firmware() -> (Firmware, TestPlatform) {
|
||||
let mut firmware = Firmware::new(None);
|
||||
firmware.state = FirmwareState::HubHome(HubHomeStep);
|
||||
firmware.state_initialized = false;
|
||||
|
||||
(firmware, Hardware::new())
|
||||
(firmware, TestPlatform::new())
|
||||
}
|
||||
|
||||
fn focus_window(firmware: &mut Firmware, owner: WindowOwner) {
|
||||
@ -306,11 +306,11 @@ mod tests {
|
||||
#[test]
|
||||
fn load_cartridge_transitions_to_app_crashes_when_vm_init_fails() {
|
||||
let mut firmware = Firmware::new(None);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
|
||||
firmware.load_cartridge(invalid_game_cartridge());
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
match &firmware.state {
|
||||
FirmwareState::AppCrashes(step) => match &step.report {
|
||||
@ -326,18 +326,18 @@ mod tests {
|
||||
#[test]
|
||||
fn game_running_transitions_to_app_crashes_when_runtime_surfaces_trap() {
|
||||
let mut firmware = Firmware::new(None);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
|
||||
firmware.load_cartridge(trapping_game_cartridge());
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
let task_id = match &firmware.state {
|
||||
FirmwareState::GameRunning(step) => step.task_id,
|
||||
other => panic!("expected GameRunning state, got {:?}", other),
|
||||
};
|
||||
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
match &firmware.state {
|
||||
FirmwareState::AppCrashes(step) => match &step.report {
|
||||
@ -359,11 +359,11 @@ mod tests {
|
||||
#[test]
|
||||
fn reset_routes_hub_boot_target_to_splash_screen() {
|
||||
let mut firmware = Firmware::new(None);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
|
||||
firmware.boot_target = BootTarget::Hub;
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::SplashScreen(_)));
|
||||
}
|
||||
@ -371,12 +371,12 @@ mod tests {
|
||||
#[test]
|
||||
fn reset_routes_cartridge_boot_target_to_launch_hub() {
|
||||
let mut firmware = Firmware::new(None);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
|
||||
firmware.boot_target =
|
||||
BootTarget::Cartridge { path: "missing-cart".into(), debug: false, debug_port: 7777 };
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::LaunchHub(_)));
|
||||
}
|
||||
@ -384,11 +384,11 @@ mod tests {
|
||||
#[test]
|
||||
fn load_cartridge_routes_system_apps_to_system_running() {
|
||||
let mut firmware = Firmware::new(None);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
|
||||
firmware.load_cartridge(valid_cartridge(AppMode::Shell));
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::ShellRunning(_)));
|
||||
assert!(firmware.os.windows().focused_window().is_some());
|
||||
@ -398,18 +398,18 @@ mod tests {
|
||||
#[test]
|
||||
fn system_running_ticks_focused_system_app_through_hub_pipeline() {
|
||||
let mut firmware = Firmware::new(None);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
|
||||
firmware.load_cartridge(valid_cartridge(AppMode::Shell));
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
let task_id = match &firmware.state {
|
||||
FirmwareState::ShellRunning(step) => step.task_id,
|
||||
other => panic!("expected ShellRunning state, got {:?}", other),
|
||||
};
|
||||
|
||||
let tick_index_before_system_update = firmware.os.vm().tick_index();
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::ShellRunning(_)));
|
||||
assert!(firmware.os.windows().focused_window_belongs_to_task(task_id));
|
||||
@ -418,10 +418,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn hub_home_does_not_launch_shell_before_home_input_is_armed() {
|
||||
let (mut firmware, mut hardware) = hub_home_firmware();
|
||||
let (mut firmware, mut platform) = hub_home_firmware();
|
||||
let signals = InputSignals { f_signal: true, x_pos: 112, y_pos: 108, ..Default::default() };
|
||||
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
||||
assert_eq!(firmware.os.windows().window_count(), 0);
|
||||
@ -429,11 +429,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn hub_home_click_launches_shell_a_as_native_shell_task_window() {
|
||||
let (mut firmware, mut hardware) = hub_home_firmware();
|
||||
firmware.tick(&InputSignals::default(), &mut hardware);
|
||||
let (mut firmware, mut platform) = hub_home_firmware();
|
||||
firmware.tick(&InputSignals::default(), &mut platform);
|
||||
|
||||
let signals = InputSignals { f_signal: true, x_pos: 112, y_pos: 108, ..Default::default() };
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
let task_id = match &firmware.state {
|
||||
FirmwareState::ShellRunning(step) => step.task_id,
|
||||
@ -452,11 +452,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn hub_home_click_launches_shell_b_as_native_shell_task_window() {
|
||||
let (mut firmware, mut hardware) = hub_home_firmware();
|
||||
firmware.tick(&InputSignals::default(), &mut hardware);
|
||||
let (mut firmware, mut platform) = hub_home_firmware();
|
||||
firmware.tick(&InputSignals::default(), &mut platform);
|
||||
|
||||
let signals = InputSignals { f_signal: true, x_pos: 256, y_pos: 108, ..Default::default() };
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
let task_id = match &firmware.state {
|
||||
FirmwareState::ShellRunning(step) => step.task_id,
|
||||
@ -475,10 +475,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn shell_running_start_close_uses_lifecycle_and_returns_hub_home() {
|
||||
let (mut firmware, mut hardware, _signals, task_id) = load_shell_running_firmware();
|
||||
let (mut firmware, mut platform, _signals, task_id) = load_shell_running_firmware();
|
||||
let close_signals = InputSignals { start_signal: true, ..Default::default() };
|
||||
|
||||
firmware.tick(&close_signals, &mut hardware);
|
||||
firmware.tick(&close_signals, &mut platform);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
||||
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
|
||||
@ -490,11 +490,11 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn shell_running_close_click_uses_lifecycle_and_returns_hub_home() {
|
||||
let (mut firmware, mut hardware, _signals, task_id) = load_shell_running_firmware();
|
||||
let (mut firmware, mut platform, _signals, task_id) = load_shell_running_firmware();
|
||||
let close_signals =
|
||||
InputSignals { f_signal: true, x_pos: 420, y_pos: 24, ..Default::default() };
|
||||
|
||||
firmware.tick(&close_signals, &mut hardware);
|
||||
firmware.tick(&close_signals, &mut platform);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
||||
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
|
||||
@ -506,10 +506,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn shell_running_closes_task_and_returns_hub_home_without_focused_window() {
|
||||
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware();
|
||||
let (mut firmware, mut platform, signals, task_id) = load_shell_running_firmware();
|
||||
|
||||
firmware.os.windows().remove_all_windows();
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
||||
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
|
||||
@ -521,10 +521,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn shell_running_closes_task_and_returns_hub_home_for_focused_hub_window() {
|
||||
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware();
|
||||
let (mut firmware, mut platform, signals, task_id) = load_shell_running_firmware();
|
||||
|
||||
focus_window(&mut firmware, WindowOwner::Hub);
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
||||
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
|
||||
@ -536,10 +536,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn shell_running_closes_task_and_returns_hub_home_for_focused_overlay_window() {
|
||||
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware();
|
||||
let (mut firmware, mut platform, signals, task_id) = load_shell_running_firmware();
|
||||
|
||||
focus_window(&mut firmware, WindowOwner::Overlay);
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
||||
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
|
||||
@ -551,10 +551,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn shell_running_closes_task_and_returns_hub_home_for_other_task_window() {
|
||||
let (mut firmware, mut hardware, signals, task_id) = load_shell_running_firmware();
|
||||
let (mut firmware, mut platform, signals, task_id) = load_shell_running_firmware();
|
||||
|
||||
focus_window(&mut firmware, WindowOwner::Task(TaskId(task_id.0 + 1)));
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::HubHome(_)));
|
||||
assert_eq!(firmware.os.lifecycle().task_state(task_id), Some(TaskState::Closed));
|
||||
@ -567,11 +567,11 @@ mod tests {
|
||||
#[test]
|
||||
fn load_cartridge_routes_game_apps_to_game_running() {
|
||||
let mut firmware = Firmware::new(None);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
|
||||
firmware.load_cartridge(valid_cartridge(AppMode::Game));
|
||||
firmware.tick(&signals, &mut hardware);
|
||||
firmware.tick(&signals, &mut platform);
|
||||
|
||||
assert!(matches!(firmware.state, FirmwareState::GameRunning(_)));
|
||||
assert_eq!(firmware.os.windows().window_count(), 0);
|
||||
|
||||
@ -21,13 +21,16 @@ impl AppCrashesStep {
|
||||
|
||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||
// Update peripherals for input on the crash screen
|
||||
ctx.hw.pad_mut().begin_frame(ctx.signals);
|
||||
ctx.platform.input_mut().pad_mut().begin_frame(ctx.signals);
|
||||
|
||||
let packet = ShellUiFramePacket::new(vec![GfxUiCommand::Clear { color: Color::RED }]);
|
||||
ctx.hw.publish_render_submission(&RenderSubmission::shell_ui(FrameId::ZERO, packet));
|
||||
ctx.platform
|
||||
.render_submission_sink()
|
||||
.submit_render_submission(RenderSubmission::shell_ui(FrameId::ZERO, packet))
|
||||
.expect("firmware crash render submission should publish");
|
||||
|
||||
// If START is pressed, return to the Hub
|
||||
if ctx.hw.pad().start().down {
|
||||
if ctx.platform.input().pad().start().down {
|
||||
return Some(FirmwareState::LaunchHub(LaunchHubStep));
|
||||
}
|
||||
|
||||
|
||||
@ -36,7 +36,7 @@ impl GameRunningStep {
|
||||
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||
}
|
||||
|
||||
let result = ctx.os.vm().tick(ctx.vm, ctx.signals, ctx.hw);
|
||||
let result = ctx.os.vm().tick(ctx.vm, ctx.signals, ctx.platform);
|
||||
|
||||
if let Some(report) = result {
|
||||
let _ = ctx.os.lifecycle().crash_task(self.task_id, Some(&report));
|
||||
|
||||
@ -16,7 +16,7 @@ impl HubHomeStep {
|
||||
}
|
||||
|
||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||
let outcome = ctx.hub.update_shell_profile(ctx.os, ctx.vm, ctx.signals, ctx.hw);
|
||||
let outcome = ctx.hub.update_shell_profile(ctx.os, ctx.vm, ctx.signals, ctx.platform);
|
||||
if let Some(report) = outcome.crash {
|
||||
return Some(FirmwareState::AppCrashes(AppCrashesStep { report }));
|
||||
}
|
||||
|
||||
@ -30,7 +30,7 @@ impl LoadCartridgeStep {
|
||||
);
|
||||
|
||||
// Initialize Asset Manager
|
||||
ctx.hw.assets_mut().initialize_for_cartridge(
|
||||
ctx.platform.assets_mut().initialize_for_cartridge(
|
||||
self.cartridge.asset_table.clone(),
|
||||
self.cartridge.preload.clone(),
|
||||
self.cartridge.assets.clone(),
|
||||
|
||||
@ -59,7 +59,7 @@ impl ShellRunningStep {
|
||||
}
|
||||
}
|
||||
|
||||
let outcome = ctx.hub.update_shell_profile(ctx.os, ctx.vm, ctx.signals, ctx.hw);
|
||||
let outcome = ctx.hub.update_shell_profile(ctx.os, ctx.vm, ctx.signals, ctx.platform);
|
||||
|
||||
if let Some(report) = outcome.crash {
|
||||
let _ = ctx.os.lifecycle().crash_task(self.task_id, Some(&report));
|
||||
|
||||
@ -14,7 +14,7 @@ impl SplashScreenStep {
|
||||
pub fn on_enter(&mut self, ctx: &mut PrometeuContext) {
|
||||
ctx.os.log(LogLevel::Info, LogSource::Pos, 0, "Showing SplashScreen".to_string());
|
||||
// Play sound on enter
|
||||
// ctx.hw.audio_mut().play(0, 0, 0, 255, 127, 1.0, 0, LoopMode::Off);
|
||||
// ctx.platform.audio_mut().play(0, 0, 0, 255, 127, 1.0, 0, LoopMode::Off);
|
||||
}
|
||||
|
||||
pub fn on_update(&mut self, ctx: &mut PrometeuContext) -> Option<FirmwareState> {
|
||||
@ -22,10 +22,10 @@ impl SplashScreenStep {
|
||||
const TOTAL_DURATION: u32 = 240; // 4 seconds total (updated from 2s based on total_duration logic)
|
||||
|
||||
// Update peripherals for input
|
||||
ctx.hw.pad_mut().begin_frame(ctx.signals);
|
||||
ctx.platform.input_mut().pad_mut().begin_frame(ctx.signals);
|
||||
|
||||
// Draw expanding square
|
||||
let (sw, sh) = ctx.hw.gfx().size();
|
||||
let (sw, sh) = ctx.platform.display_size();
|
||||
let max_size = (sw.min(sh) as i32 / 2).max(1);
|
||||
|
||||
let current_size = if self.frame < ANIMATION_DURATION {
|
||||
@ -44,11 +44,14 @@ impl SplashScreenStep {
|
||||
color: Color::WHITE,
|
||||
},
|
||||
]);
|
||||
ctx.hw.publish_render_submission(&RenderSubmission::shell_ui(FrameId::ZERO, packet));
|
||||
ctx.platform
|
||||
.render_submission_sink()
|
||||
.submit_render_submission(RenderSubmission::shell_ui(FrameId::ZERO, packet))
|
||||
.expect("firmware splash render submission should publish");
|
||||
|
||||
// Transition logic
|
||||
// If any button is pressed at any time after the animation ends
|
||||
if self.frame >= ANIMATION_DURATION && ctx.hw.pad().any() {
|
||||
if self.frame >= ANIMATION_DURATION && ctx.platform.input().pad().any() {
|
||||
return Some(FirmwareState::LaunchHub(LaunchHubStep));
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::firmware::boot_target::BootTarget;
|
||||
use prometeu_hal::{HardwareBridge, InputSignals};
|
||||
use prometeu_hal::{InputSignals, RuntimePlatform};
|
||||
use prometeu_system::{PrometeuHub, SystemOS};
|
||||
use prometeu_vm::VirtualMachine;
|
||||
|
||||
@ -9,5 +9,5 @@ pub struct PrometeuContext<'a> {
|
||||
pub hub: &'a mut PrometeuHub,
|
||||
pub boot_target: &'a BootTarget,
|
||||
pub signals: &'a InputSignals,
|
||||
pub hw: &'a mut dyn HardwareBridge,
|
||||
pub platform: &'a mut dyn RuntimePlatform,
|
||||
}
|
||||
|
||||
@ -1,39 +0,0 @@
|
||||
use crate::asset_bridge::AssetBridge;
|
||||
use crate::audio_bridge::AudioBridge;
|
||||
use crate::gfx_bridge::GfxBridge;
|
||||
use crate::pad_bridge::PadBridge;
|
||||
use crate::render_submission::{Game2DFramePacket, RenderSubmission};
|
||||
use crate::sprite::Sprite;
|
||||
use crate::touch_bridge::TouchBridge;
|
||||
|
||||
/// Legacy migration scaffold for code that has not moved to `platform` facades yet.
|
||||
///
|
||||
/// `HardwareBridge` is intentionally not the final runtime-facing contract. New
|
||||
/// platform boundaries belong in `crate::platform`, and this trait must be
|
||||
/// removed after runtime, firmware, host, and tests finish migrating.
|
||||
pub trait HardwareBridge {
|
||||
fn begin_frame(&mut self);
|
||||
fn bind_scene(&mut self, scene_bank_id: usize) -> bool;
|
||||
fn unbind_scene(&mut self);
|
||||
fn set_camera(&mut self, x: i32, y: i32);
|
||||
fn emit_sprite(&mut self, sprite: Sprite) -> bool;
|
||||
fn close_game2d_packet(&self) -> Game2DFramePacket;
|
||||
fn publish_render_submission(&mut self, submission: &RenderSubmission);
|
||||
fn render_frame(&mut self);
|
||||
fn has_glyph_bank(&self, bank_id: usize) -> bool;
|
||||
|
||||
fn gfx(&self) -> &dyn GfxBridge;
|
||||
fn gfx_mut(&mut self) -> &mut dyn GfxBridge;
|
||||
|
||||
fn audio(&self) -> &dyn AudioBridge;
|
||||
fn audio_mut(&mut self) -> &mut dyn AudioBridge;
|
||||
|
||||
fn pad(&self) -> &dyn PadBridge;
|
||||
fn pad_mut(&mut self) -> &mut dyn PadBridge;
|
||||
|
||||
fn touch(&self) -> &dyn TouchBridge;
|
||||
fn touch_mut(&mut self) -> &mut dyn TouchBridge;
|
||||
|
||||
fn assets(&self) -> &dyn AssetBridge;
|
||||
fn assets_mut(&mut self) -> &mut dyn AssetBridge;
|
||||
}
|
||||
@ -1,19 +1,23 @@
|
||||
use crate::hardware_bridge::HardwareBridge;
|
||||
use crate::platform::RuntimePlatform;
|
||||
use crate::vm_fault::VmFault;
|
||||
|
||||
pub struct HostContext<'a> {
|
||||
pub hw: Option<&'a mut dyn HardwareBridge>,
|
||||
pub platform: Option<&'a mut dyn RuntimePlatform>,
|
||||
}
|
||||
|
||||
impl<'a> HostContext<'a> {
|
||||
pub fn new(hw: Option<&'a mut dyn HardwareBridge>) -> Self {
|
||||
Self { hw }
|
||||
pub fn new(platform: Option<&'a mut dyn RuntimePlatform>) -> Self {
|
||||
Self { platform }
|
||||
}
|
||||
|
||||
pub fn new_platform(platform: Option<&'a mut dyn RuntimePlatform>) -> Self {
|
||||
Self::new(platform)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn require_hw(&mut self) -> Result<&mut dyn HardwareBridge, VmFault> {
|
||||
match &mut self.hw {
|
||||
Some(hw) => Ok(*hw),
|
||||
pub fn require_platform(&mut self) -> Result<&mut dyn RuntimePlatform, VmFault> {
|
||||
match &mut self.platform {
|
||||
Some(platform) => Ok(*platform),
|
||||
None => Err(VmFault::Unavailable),
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,6 @@ pub mod debugger_protocol;
|
||||
pub mod gfx_bridge;
|
||||
pub mod glyph;
|
||||
pub mod glyph_bank;
|
||||
pub mod hardware_bridge;
|
||||
pub mod host_context;
|
||||
pub mod host_return;
|
||||
pub mod input_signals;
|
||||
@ -40,7 +39,6 @@ pub use asset_bridge::AssetBridge;
|
||||
pub use audio_bridge::{AudioBridge, AudioOpStatus, LoopMode};
|
||||
pub use composer_status::ComposerOpStatus;
|
||||
pub use gfx_bridge::{BlendMode, GfxBridge, GfxOpStatus};
|
||||
pub use hardware_bridge::HardwareBridge;
|
||||
pub use host_context::{HostContext, HostContextProvider};
|
||||
pub use host_return::HostReturn;
|
||||
pub use input_signals::InputSignals;
|
||||
@ -49,8 +47,8 @@ pub use native_interface::{NativeInterface, SyscallId};
|
||||
pub use pad_bridge::PadBridge;
|
||||
pub use platform::{
|
||||
AssetStoragePlatform, AudioPlatform, ClockPacingPlatform, Game2DFrameComposer, InputPlatform,
|
||||
RenderBackend, RenderSubmissionSink, RenderSubmitError, RuntimePlatform, TelemetryPlatform,
|
||||
TestPlatform,
|
||||
NoopTelemetryPlatform, RenderBackend, RenderSubmissionSink, RenderSubmitError, RuntimePlatform,
|
||||
TelemetryPlatform, TestPlatform,
|
||||
};
|
||||
pub use render_submission::{
|
||||
BoundScenePacket, Camera2D, ComposerFramePacket, FrameId, Game2DFramePacket, GameSpritePacket,
|
||||
|
||||
@ -31,7 +31,6 @@ pub trait Game2DFrameComposer {
|
||||
fn set_camera(&mut self, x: i32, y: i32) -> ComposerOpStatus;
|
||||
fn emit_sprite(&mut self, sprite: Sprite) -> ComposerOpStatus;
|
||||
fn close_game2d_packet(&self) -> Game2DFramePacket;
|
||||
fn has_glyph_bank(&self, bank_id: usize) -> bool;
|
||||
}
|
||||
|
||||
pub trait AudioPlatform: AudioBridge {}
|
||||
@ -55,16 +54,25 @@ pub trait TelemetryPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct NoopTelemetryPlatform;
|
||||
|
||||
impl TelemetryPlatform for NoopTelemetryPlatform {}
|
||||
|
||||
pub trait RuntimePlatform {
|
||||
fn display_size(&self) -> (usize, usize) {
|
||||
(480, 270)
|
||||
}
|
||||
|
||||
fn render_submission_sink(&mut self) -> &mut dyn RenderSubmissionSink;
|
||||
fn render_backend(&mut self) -> &mut dyn RenderBackend;
|
||||
fn game2d_frame_composer(&mut self) -> &mut dyn Game2DFrameComposer;
|
||||
fn audio(&self) -> &dyn AudioPlatform;
|
||||
fn audio_mut(&mut self) -> &mut dyn AudioPlatform;
|
||||
fn audio(&self) -> &dyn AudioBridge;
|
||||
fn audio_mut(&mut self) -> &mut dyn AudioBridge;
|
||||
fn input(&self) -> &dyn InputPlatform;
|
||||
fn input_mut(&mut self) -> &mut dyn InputPlatform;
|
||||
fn assets(&self) -> &dyn AssetStoragePlatform;
|
||||
fn assets_mut(&mut self) -> &mut dyn AssetStoragePlatform;
|
||||
fn assets(&self) -> &dyn AssetBridge;
|
||||
fn assets_mut(&mut self) -> &mut dyn AssetBridge;
|
||||
fn telemetry(&self) -> &dyn TelemetryPlatform;
|
||||
}
|
||||
|
||||
@ -114,10 +122,6 @@ mod tests {
|
||||
fn close_game2d_packet(&self) -> Game2DFramePacket {
|
||||
Game2DFramePacket::default()
|
||||
}
|
||||
|
||||
fn has_glyph_bank(&self, _bank_id: usize) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -3,7 +3,7 @@ use crate::os::SystemOS;
|
||||
use prometeu_hal::app_mode::AppMode;
|
||||
use prometeu_hal::cartridge::Cartridge;
|
||||
use prometeu_hal::telemetry::{CertificationConfig, TelemetryFrame};
|
||||
use prometeu_hal::{HardwareBridge, InputSignals};
|
||||
use prometeu_hal::{InputSignals, RuntimePlatform};
|
||||
use prometeu_vm::VirtualMachine;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
@ -25,7 +25,7 @@ impl<'a> VmFacade<'a> {
|
||||
&mut self,
|
||||
vm: &mut VirtualMachine,
|
||||
signals: &InputSignals,
|
||||
hw: &mut dyn HardwareBridge,
|
||||
platform: &mut dyn RuntimePlatform,
|
||||
) -> Option<CrashReport> {
|
||||
self.os.vm_runtime.tick(
|
||||
&mut self.os.log_service,
|
||||
@ -36,7 +36,7 @@ impl<'a> VmFacade<'a> {
|
||||
&mut self.os.next_handle,
|
||||
vm,
|
||||
signals,
|
||||
hw,
|
||||
platform,
|
||||
)
|
||||
}
|
||||
|
||||
@ -47,9 +47,9 @@ impl<'a> VmFacade<'a> {
|
||||
pub fn debug_step_instruction(
|
||||
&mut self,
|
||||
vm: &mut VirtualMachine,
|
||||
hw: &mut dyn HardwareBridge,
|
||||
platform: &mut dyn RuntimePlatform,
|
||||
) -> Option<CrashReport> {
|
||||
self.os.vm_runtime.debug_step_instruction(&mut self.os.log_service, vm, hw)
|
||||
self.os.vm_runtime.debug_step_instruction(&mut self.os.log_service, vm, platform)
|
||||
}
|
||||
|
||||
pub fn paused(&self) -> bool {
|
||||
|
||||
@ -2,7 +2,8 @@ use crate::{CrashReport, SystemOS};
|
||||
use prometeu_hal::color::Color;
|
||||
use prometeu_hal::primitives::Rect;
|
||||
use prometeu_hal::{
|
||||
FrameId, GfxUiCommand, HardwareBridge, InputSignals, RenderSubmission, ShellUiFramePacket,
|
||||
FrameId, GfxUiCommand, InputPlatform, InputSignals, RenderSubmission, RuntimePlatform,
|
||||
ShellUiFramePacket,
|
||||
};
|
||||
use prometeu_vm::VirtualMachine;
|
||||
|
||||
@ -100,31 +101,35 @@ impl PrometeuHub {
|
||||
pub fn gui_update(
|
||||
&mut self,
|
||||
os: &mut SystemOS,
|
||||
hw: &mut dyn HardwareBridge,
|
||||
platform: &mut dyn RuntimePlatform,
|
||||
) -> Option<SystemProfileAction> {
|
||||
let in_shell = os.windows().focused_window().is_some();
|
||||
|
||||
if !in_shell {
|
||||
if !self.home_input_armed {
|
||||
self.home_input_armed = !activation_input_down(hw);
|
||||
self.home_input_armed = !activation_input_down(platform.input());
|
||||
return None;
|
||||
}
|
||||
} else {
|
||||
self.home_input_armed = false;
|
||||
}
|
||||
|
||||
if let Some(action) =
|
||||
action_for_click(in_shell, hw.touch().x(), hw.touch().y(), hw.touch().f().pressed)
|
||||
{
|
||||
let input = platform.input();
|
||||
if let Some(action) = action_for_click(
|
||||
in_shell,
|
||||
input.touch().x(),
|
||||
input.touch().y(),
|
||||
input.touch().f().pressed,
|
||||
) {
|
||||
return Some(action);
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn render(&mut self, os: &mut SystemOS, hw: &mut dyn HardwareBridge) {
|
||||
let pointer_x = hw.touch().x();
|
||||
let pointer_y = hw.touch().y();
|
||||
pub fn render(&mut self, os: &mut SystemOS, platform: &mut dyn RuntimePlatform) {
|
||||
let pointer_x = platform.input().touch().x();
|
||||
let pointer_y = platform.input().touch().y();
|
||||
|
||||
let packet = if os.windows().window_count() == 0 {
|
||||
render_home_packet(pointer_x, pointer_y)
|
||||
@ -142,7 +147,10 @@ impl PrometeuHub {
|
||||
}
|
||||
ShellUiFramePacket::new(commands)
|
||||
};
|
||||
hw.publish_render_submission(&RenderSubmission::shell_ui(FrameId::ZERO, packet));
|
||||
platform
|
||||
.render_submission_sink()
|
||||
.submit_render_submission(RenderSubmission::shell_ui(FrameId::ZERO, packet))
|
||||
.expect("hub render submission should publish");
|
||||
}
|
||||
|
||||
pub fn update_shell_profile(
|
||||
@ -150,20 +158,20 @@ impl PrometeuHub {
|
||||
os: &mut SystemOS,
|
||||
vm: &mut VirtualMachine,
|
||||
signals: &InputSignals,
|
||||
hw: &mut dyn HardwareBridge,
|
||||
platform: &mut dyn RuntimePlatform,
|
||||
) -> SystemProfileUpdate {
|
||||
let mut crash = None;
|
||||
let mut action = self.gui_update(os, hw);
|
||||
let mut action = self.gui_update(os, platform);
|
||||
|
||||
if os.windows().focused_window().is_some() {
|
||||
if hw.pad().start().down {
|
||||
if platform.input().pad().start().down {
|
||||
action = Some(SystemProfileAction::CloseShell);
|
||||
} else if action != Some(SystemProfileAction::CloseShell) {
|
||||
crash = os.vm().tick(vm, signals, hw);
|
||||
crash = os.vm().tick(vm, signals, platform);
|
||||
}
|
||||
}
|
||||
|
||||
self.render(os, hw);
|
||||
self.render(os, platform);
|
||||
|
||||
SystemProfileUpdate { crash, action }
|
||||
}
|
||||
@ -188,8 +196,11 @@ fn rect_contains(rect: Rect, x: i32, y: i32) -> bool {
|
||||
x >= rect.x && x < rect.x + rect.w && y >= rect.y && y < rect.y + rect.h
|
||||
}
|
||||
|
||||
fn activation_input_down(hw: &dyn HardwareBridge) -> bool {
|
||||
hw.touch().f().down || hw.pad().a().down || hw.pad().b().down || hw.pad().start().down
|
||||
fn activation_input_down(input: &dyn InputPlatform) -> bool {
|
||||
input.touch().f().down
|
||||
|| input.pad().a().down
|
||||
|| input.pad().b().down
|
||||
|| input.pad().start().down
|
||||
}
|
||||
|
||||
fn render_home_packet(pointer_x: i32, pointer_y: i32) -> ShellUiFramePacket {
|
||||
|
||||
@ -179,7 +179,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
|
||||
self.ensure_game_profile_syscall(syscall)?;
|
||||
|
||||
let hw = ctx.require_hw()?;
|
||||
let platform = ctx.require_platform()?;
|
||||
|
||||
match syscall {
|
||||
Syscall::SystemHasCart => unreachable!(),
|
||||
@ -187,7 +187,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
Syscall::GfxClear => {
|
||||
let color = self.get_color(expect_int(args, 0)?)?;
|
||||
self.gfx2d_commands.push(Gfx2dCommand::Clear { color });
|
||||
hw.gfx_mut().clear(color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxUiClear => {
|
||||
@ -199,7 +198,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
}
|
||||
let color = self.get_color(expect_int(args, 0)?)?;
|
||||
self.gfxui_commands.push(GfxUiCommand::Clear { color });
|
||||
hw.gfx_mut().clear(color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxFillRect => {
|
||||
@ -210,7 +208,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
let color = self.get_color(expect_int(args, 4)?)?;
|
||||
self.gfx2d_commands
|
||||
.push(Gfx2dCommand::FillRect { rect: Rect { x, y, w, h }, color });
|
||||
hw.gfx_mut().fill_rect(x, y, w, h, color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxUiFillRect => {
|
||||
@ -227,7 +224,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
let color = self.get_color(expect_int(args, 4)?)?;
|
||||
self.gfxui_commands
|
||||
.push(GfxUiCommand::FillRect { rect: Rect { x, y, w, h }, color });
|
||||
hw.gfx_mut().fill_rect(x, y, w, h, color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxDrawLine => {
|
||||
@ -243,7 +239,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
y1: y2,
|
||||
color,
|
||||
});
|
||||
hw.gfx_mut().draw_line(x1, y1, x2, y2, color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxUiDrawLine => {
|
||||
@ -265,7 +260,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
y1: y2,
|
||||
color,
|
||||
});
|
||||
hw.gfx_mut().draw_line(x1, y1, x2, y2, color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxDrawCircle => {
|
||||
@ -274,7 +268,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
let r = expect_int(args, 2)? as i32;
|
||||
let color = self.get_color(expect_int(args, 3)?)?;
|
||||
self.gfx2d_commands.push(Gfx2dCommand::DrawCircle { x, y, radius: r, color });
|
||||
hw.gfx_mut().draw_circle(x, y, r, color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxUiDrawCircle => {
|
||||
@ -289,7 +282,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
let r = expect_int(args, 2)? as i32;
|
||||
let color = self.get_color(expect_int(args, 3)?)?;
|
||||
self.gfxui_commands.push(GfxUiCommand::DrawCircle { x, y, radius: r, color });
|
||||
hw.gfx_mut().draw_circle(x, y, r, color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxDrawDisc => {
|
||||
@ -305,7 +297,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
border_color,
|
||||
fill_color,
|
||||
});
|
||||
hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxUiDrawDisc => {
|
||||
@ -327,7 +318,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
border_color,
|
||||
fill_color,
|
||||
});
|
||||
hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxDrawSquare => {
|
||||
@ -342,7 +332,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
border_color,
|
||||
fill_color,
|
||||
});
|
||||
hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxUiDrawSquare => {
|
||||
@ -363,7 +352,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
border_color,
|
||||
fill_color,
|
||||
});
|
||||
hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxDrawText => {
|
||||
@ -372,7 +360,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
let msg = expect_string(args, 2, "message")?;
|
||||
let color = self.get_color(expect_int(args, 3)?)?;
|
||||
self.gfx2d_commands.push(Gfx2dCommand::DrawText { x, y, text: msg.clone(), color });
|
||||
hw.gfx_mut().draw_text(x, y, &msg, color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::GfxUiDrawText => {
|
||||
@ -387,7 +374,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
let msg = expect_string(args, 2, "message")?;
|
||||
let color = self.get_color(expect_int(args, 3)?)?;
|
||||
self.gfxui_commands.push(GfxUiCommand::DrawText { x, y, text: msg.clone(), color });
|
||||
hw.gfx_mut().draw_text(x, y, &msg, color);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::ComposerBindScene => {
|
||||
@ -399,23 +385,23 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
}
|
||||
};
|
||||
|
||||
let bound = catch_unwind(AssertUnwindSafe(|| hw.bind_scene(scene_bank_id)))
|
||||
.map_err(|payload| VmFault::Panic(Self::panic_payload_to_string(payload)))?;
|
||||
let status = catch_unwind(AssertUnwindSafe(|| {
|
||||
platform.game2d_frame_composer().bind_scene(scene_bank_id)
|
||||
}))
|
||||
.map_err(|payload| VmFault::Panic(Self::panic_payload_to_string(payload)))?;
|
||||
|
||||
let status =
|
||||
if bound { ComposerOpStatus::Ok } else { ComposerOpStatus::SceneUnavailable };
|
||||
ret.push_int(status as i64);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::ComposerUnbindScene => {
|
||||
hw.unbind_scene();
|
||||
platform.game2d_frame_composer().unbind_scene();
|
||||
ret.push_int(ComposerOpStatus::Ok as i64);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::ComposerSetCamera => {
|
||||
let x = Self::int_arg_to_i32_trap(expect_int(args, 0)?, "camera x")?;
|
||||
let y = Self::int_arg_to_i32_trap(expect_int(args, 1)?, "camera y")?;
|
||||
hw.set_camera(x, y);
|
||||
platform.game2d_frame_composer().set_camera(x, y);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::ComposerEmitSprite => {
|
||||
@ -463,12 +449,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
}
|
||||
};
|
||||
|
||||
if !hw.has_glyph_bank(bank_id as usize) {
|
||||
ret.push_int(ComposerOpStatus::BankInvalid as i64);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let emitted = hw.emit_sprite(Sprite {
|
||||
let status = platform.game2d_frame_composer().emit_sprite(Sprite {
|
||||
glyph: Glyph { glyph_id, palette_id },
|
||||
x,
|
||||
y,
|
||||
@ -479,8 +460,6 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
flip_y,
|
||||
priority,
|
||||
});
|
||||
let status =
|
||||
if emitted { ComposerOpStatus::Ok } else { ComposerOpStatus::SpriteOverflow };
|
||||
ret.push_int(status as i64);
|
||||
Ok(())
|
||||
}
|
||||
@ -507,7 +486,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let status = hw.audio_mut().play(
|
||||
let status = platform.audio_mut().play(
|
||||
0,
|
||||
sample_id_raw as u16,
|
||||
voice_id_raw as usize,
|
||||
@ -537,7 +516,8 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if hw.assets().slot_info(SlotRef::audio(bank_id as usize)).asset_id.is_none() {
|
||||
if platform.assets().slot_info(SlotRef::audio(bank_id as usize)).asset_id.is_none()
|
||||
{
|
||||
ret.push_int(AudioOpStatus::BankInvalid as i64);
|
||||
return Ok(());
|
||||
}
|
||||
@ -553,7 +533,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let status = hw.audio_mut().play(
|
||||
let status = platform.audio_mut().play(
|
||||
bank_id,
|
||||
sample_id_raw as u16,
|
||||
voice_id_raw as usize,
|
||||
@ -750,7 +730,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
})?;
|
||||
let slot_index = expect_int(args, 1)? as usize;
|
||||
|
||||
match hw.assets().load(asset_id, slot_index) {
|
||||
match platform.assets().load(asset_id, slot_index) {
|
||||
Ok(handle) => {
|
||||
ret.push_int(AssetOpStatus::Ok as i64);
|
||||
ret.push_int(handle as i64);
|
||||
@ -764,16 +744,16 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
}
|
||||
}
|
||||
Syscall::AssetStatus => {
|
||||
ret.push_int(hw.assets().status(expect_int(args, 0)? as u32) as i64);
|
||||
ret.push_int(platform.assets().status(expect_int(args, 0)? as u32) as i64);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::AssetCommit => {
|
||||
let status = hw.assets().commit(expect_int(args, 0)? as u32);
|
||||
let status = platform.assets().commit(expect_int(args, 0)? as u32);
|
||||
ret.push_int(status as i64);
|
||||
Ok(())
|
||||
}
|
||||
Syscall::AssetCancel => {
|
||||
let status = hw.assets().cancel(expect_int(args, 0)? as u32);
|
||||
let status = platform.assets().cancel(expect_int(args, 0)? as u32);
|
||||
ret.push_int(status as i64);
|
||||
Ok(())
|
||||
}
|
||||
@ -783,7 +763,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
|
||||
1 => BankType::SOUNDS,
|
||||
_ => return Err(VmFault::Trap(TRAP_TYPE, "Invalid asset type".to_string())),
|
||||
};
|
||||
let telemetry = hw
|
||||
let telemetry = platform
|
||||
.assets()
|
||||
.bank_telemetry()
|
||||
.into_iter()
|
||||
|
||||
@ -5,11 +5,10 @@ use prometeu_bytecode::TRAP_TYPE;
|
||||
use prometeu_bytecode::Value;
|
||||
use prometeu_bytecode::assembler::assemble;
|
||||
use prometeu_bytecode::model::{BytecodeModule, ConstantPoolEntry, FunctionMeta, SyscallDecl};
|
||||
use prometeu_drivers::hardware::Hardware;
|
||||
use prometeu_drivers::TestPlatform;
|
||||
use prometeu_drivers::{GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolInstaller};
|
||||
use prometeu_hal::AudioOpStatus;
|
||||
use prometeu_hal::ComposerOpStatus;
|
||||
use prometeu_hal::InputSignals;
|
||||
use prometeu_hal::asset::{
|
||||
AssetCodec, AssetEntry, AssetLoadError, AssetOpStatus, BankType, LoadStatus,
|
||||
};
|
||||
@ -22,6 +21,7 @@ use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer};
|
||||
use prometeu_hal::syscalls::caps;
|
||||
use prometeu_hal::tile::Tile;
|
||||
use prometeu_hal::tilemap::TileMap;
|
||||
use prometeu_hal::{InputSignals, RuntimePlatform};
|
||||
use prometeu_vm::VmInitError;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
@ -247,7 +247,7 @@ fn tick_returns_error_when_vm_ends_slice_with_trap() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_BOOL 1\nHOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -274,7 +274,7 @@ fn tick_returns_error_when_vm_ends_slice_with_trap() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
)
|
||||
.expect("trap must surface as runtime error");
|
||||
|
||||
@ -298,7 +298,7 @@ fn tick_system_profile_rejects_gfx_game_surface() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -324,7 +324,7 @@ fn tick_system_profile_rejects_gfx_game_surface() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
)
|
||||
.expect("system gfx call must trap");
|
||||
|
||||
@ -347,7 +347,7 @@ fn tick_system_profile_rejects_composer_game_surface() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -373,7 +373,7 @@ fn tick_system_profile_rejects_composer_game_surface() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
)
|
||||
.expect("system composer call must trap");
|
||||
|
||||
@ -398,7 +398,7 @@ fn tick_game_profile_rejects_gfxui_shell_surface() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -424,7 +424,7 @@ fn tick_game_profile_rejects_gfxui_shell_surface() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
)
|
||||
.expect("game gfxui call must trap");
|
||||
|
||||
@ -447,7 +447,7 @@ fn tick_shell_profile_closes_gfxui_commands_into_shell_packet() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 287454020\nHOSTCALL 0\nFRAME_SYNC\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -472,7 +472,7 @@ fn tick_shell_profile_closes_gfxui_commands_into_shell_packet() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
|
||||
assert!(report.is_none());
|
||||
@ -483,11 +483,110 @@ fn tick_shell_profile_closes_gfxui_commands_into_shell_packet() {
|
||||
};
|
||||
assert_eq!(packet.commands.len(), 1);
|
||||
assert!(matches!(packet.commands[0], prometeu_hal::GfxUiCommand::Clear { .. }));
|
||||
assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
|
||||
assert_eq!(platform.local_hardware().gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
|
||||
assert_eq!(runtime.frame_scheduler.completed_game_frames(), 0);
|
||||
assert_eq!(runtime.frame_scheduler.active_game_frame_id(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tick_gfx2d_clear_buffers_without_immediate_pixel_mutation_before_frame_close() {
|
||||
let mut runtime = VirtualMachineRuntime::new(None);
|
||||
let mut log_service = LogService::new(4096);
|
||||
let mut fs = VirtualFS::new();
|
||||
let mut fs_state = FsState::Unmounted;
|
||||
let mut memcard = MemcardService::new();
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 287454020\nHOSTCALL 0\nJMP 0").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
code,
|
||||
vec![SyscallDecl {
|
||||
module: "gfx2d".into(),
|
||||
name: "clear".into(),
|
||||
version: 1,
|
||||
arg_slots: 1,
|
||||
ret_slots: 0,
|
||||
}],
|
||||
);
|
||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||
|
||||
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||
let report = runtime.tick(
|
||||
&mut log_service,
|
||||
&mut fs,
|
||||
&mut fs_state,
|
||||
&mut memcard,
|
||||
&mut open_files,
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut platform,
|
||||
);
|
||||
|
||||
assert!(report.is_none());
|
||||
assert!(runtime.render_manager.latest_complete_submission().is_none());
|
||||
assert!(matches!(
|
||||
runtime.gfx2d_commands.first(),
|
||||
Some(prometeu_hal::Gfx2dCommand::Clear { color })
|
||||
if *color == Color::from_raw(0x11223344)
|
||||
));
|
||||
assert_ne!(platform.local_hardware().gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tick_gfx2d_draw_text_buffers_without_immediate_pixel_mutation_before_frame_close() {
|
||||
let mut runtime = VirtualMachineRuntime::new(None);
|
||||
let mut log_service = LogService::new(4096);
|
||||
let mut fs = VirtualFS::new();
|
||||
let mut fs_state = FsState::Unmounted;
|
||||
let mut memcard = MemcardService::new();
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code =
|
||||
assemble("PUSH_I32 0\nPUSH_I32 0\nPUSH_CONST 0\nPUSH_I32 287454020\nHOSTCALL 0\nJMP 0")
|
||||
.expect("assemble");
|
||||
let program = serialized_single_function_module_with_consts(
|
||||
code,
|
||||
vec![ConstantPoolEntry::String("I".into())],
|
||||
vec![SyscallDecl {
|
||||
module: "gfx2d".into(),
|
||||
name: "draw_text".into(),
|
||||
version: 1,
|
||||
arg_slots: 4,
|
||||
ret_slots: 0,
|
||||
}],
|
||||
);
|
||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||
|
||||
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||
let report = runtime.tick(
|
||||
&mut log_service,
|
||||
&mut fs,
|
||||
&mut fs_state,
|
||||
&mut memcard,
|
||||
&mut open_files,
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut platform,
|
||||
);
|
||||
|
||||
assert!(report.is_none());
|
||||
assert!(runtime.render_manager.latest_complete_submission().is_none());
|
||||
assert!(matches!(
|
||||
runtime.gfx2d_commands.first(),
|
||||
Some(prometeu_hal::Gfx2dCommand::DrawText { x: 0, y: 0, text, color })
|
||||
if text == "I" && *color == Color::from_raw(0x11223344)
|
||||
));
|
||||
assert_ne!(platform.local_hardware().gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tick_system_profile_rejects_bank_game_surface() {
|
||||
let mut runtime = VirtualMachineRuntime::new(None);
|
||||
@ -498,7 +597,7 @@ fn tick_system_profile_rejects_bank_game_surface() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -524,7 +623,7 @@ fn tick_system_profile_rejects_bank_game_surface() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
)
|
||||
.expect("system bank call must trap");
|
||||
|
||||
@ -547,7 +646,7 @@ fn tick_system_profile_can_use_shared_log_transport() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 2\nPUSH_CONST 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module_with_consts(
|
||||
@ -573,7 +672,7 @@ fn tick_system_profile_can_use_shared_log_transport() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
|
||||
assert!(report.is_none(), "system log transport must remain internally shared");
|
||||
@ -590,7 +689,7 @@ fn tick_returns_panic_report_distinct_from_trap() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::new(assemble("HOSTCALL 0\nHALT").expect("assemble"), vec![]);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
|
||||
let report = runtime
|
||||
@ -603,7 +702,7 @@ fn tick_returns_panic_report_distinct_from_trap() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
)
|
||||
.expect("panic must surface as runtime error");
|
||||
|
||||
@ -635,12 +734,12 @@ fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() {
|
||||
let banks = Arc::new(MemoryBanks::new());
|
||||
banks.install_glyph_bank(0, Arc::new(runtime_test_glyph_bank(TileSize::Size8, 2, Color::BLUE)));
|
||||
banks.install_scene_bank(0, Arc::new(runtime_test_scene(0, 2, TileSize::Size8)));
|
||||
let mut hardware = Hardware::new_with_memory_banks(Arc::clone(&banks));
|
||||
let glyph_slot_index = hardware.assets.glyph_asset_slot_index();
|
||||
let mut platform = TestPlatform::new_with_memory_banks(Arc::clone(&banks));
|
||||
let glyph_slot_index = platform.local_hardware().assets.glyph_asset_slot_index();
|
||||
let mut slots = [None; 16];
|
||||
slots[0] = Some(0);
|
||||
glyph_slot_index.rebuild_from_slots(&slots);
|
||||
assert!(hardware.frame_composer.bind_scene(0));
|
||||
assert!(platform.local_hardware_mut().frame_composer.bind_scene(0));
|
||||
|
||||
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||
let report = runtime.tick(
|
||||
@ -652,11 +751,11 @@ fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
|
||||
assert!(report.is_none(), "frame render path must not crash");
|
||||
assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw());
|
||||
assert_eq!(platform.local_hardware().gfx.front_buffer()[0], Color::BLUE.raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -708,8 +807,8 @@ fn tick_renders_scene_through_public_composer_syscalls() {
|
||||
let banks = Arc::new(MemoryBanks::new());
|
||||
banks.install_glyph_bank(0, Arc::new(runtime_test_glyph_bank(TileSize::Size8, 2, Color::BLUE)));
|
||||
banks.install_scene_bank(0, Arc::new(runtime_test_scene(0, 2, TileSize::Size8)));
|
||||
let mut hardware = Hardware::new_with_memory_banks(Arc::clone(&banks));
|
||||
let glyph_slot_index = hardware.assets.glyph_asset_slot_index();
|
||||
let mut platform = TestPlatform::new_with_memory_banks(Arc::clone(&banks));
|
||||
let glyph_slot_index = platform.local_hardware().assets.glyph_asset_slot_index();
|
||||
let mut slots = [None; 16];
|
||||
slots[0] = Some(0);
|
||||
glyph_slot_index.rebuild_from_slots(&slots);
|
||||
@ -724,12 +823,12 @@ fn tick_renders_scene_through_public_composer_syscalls() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
|
||||
assert!(report.is_none(), "public composer path must not crash");
|
||||
assert_eq!(vm.operand_stack_top(1), vec![Value::Int64(ComposerOpStatus::Ok as i64)]);
|
||||
assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw());
|
||||
assert_eq!(platform.local_hardware().gfx.front_buffer()[0], Color::BLUE.raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -759,7 +858,7 @@ fn tick_draw_text_survives_no_scene_frame_path() {
|
||||
}],
|
||||
);
|
||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
|
||||
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||
let report = runtime.tick(
|
||||
@ -771,7 +870,7 @@ fn tick_draw_text_survives_no_scene_frame_path() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
|
||||
assert!(report.is_none(), "no-scene overlay text must not crash");
|
||||
@ -782,7 +881,7 @@ fn tick_draw_text_survives_no_scene_frame_path() {
|
||||
};
|
||||
assert_eq!(packet.gfx2d.len(), 1);
|
||||
assert!(matches!(packet.gfx2d[0], prometeu_hal::Gfx2dCommand::DrawText { .. }));
|
||||
assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
|
||||
assert_eq!(platform.local_hardware().gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -808,7 +907,7 @@ fn tick_game_submissions_are_immutable_and_latest_complete_wins() {
|
||||
}],
|
||||
);
|
||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
|
||||
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||
let first_report = runtime.tick(
|
||||
@ -820,7 +919,7 @@ fn tick_game_submissions_are_immutable_and_latest_complete_wins() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(first_report.is_none());
|
||||
let first_submission =
|
||||
@ -834,7 +933,7 @@ fn tick_game_submissions_are_immutable_and_latest_complete_wins() {
|
||||
first_packet.gfx2d[0],
|
||||
prometeu_hal::Gfx2dCommand::Clear { color } if color == Color::from_raw(0x11223344)
|
||||
));
|
||||
assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
|
||||
assert_eq!(platform.local_hardware().gfx.front_buffer()[0], Color::from_raw(0x11223344).raw());
|
||||
assert_eq!(runtime.frame_scheduler.completed_game_frames(), 1);
|
||||
|
||||
let second_packet = prometeu_hal::Game2DFramePacket::new(
|
||||
@ -858,8 +957,11 @@ fn tick_game_submissions_are_immutable_and_latest_complete_wins() {
|
||||
first_packet.gfx2d[0],
|
||||
prometeu_hal::Gfx2dCommand::Clear { color } if color == Color::from_raw(0x11223344)
|
||||
));
|
||||
prometeu_hal::HardwareBridge::publish_render_submission(&mut hardware, latest);
|
||||
assert_eq!(hardware.gfx.front_buffer()[0], Color::from_raw(0x55667788).raw());
|
||||
platform
|
||||
.render_submission_sink()
|
||||
.submit_render_submission(latest.clone())
|
||||
.expect("local sink should publish latest submission");
|
||||
assert_eq!(platform.local_hardware().gfx.front_buffer()[0], Color::from_raw(0x55667788).raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -961,7 +1063,7 @@ fn tick_numeric_happy_path_records_zero_internal_allocations() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code =
|
||||
assemble("PUSH_I32 1\nPUSH_I32 2\nADD\nPOP_N 1\nFRAME_SYNC\nHALT").expect("assemble");
|
||||
@ -978,7 +1080,7 @@ fn tick_numeric_happy_path_records_zero_internal_allocations() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
|
||||
assert!(report.is_none());
|
||||
@ -997,7 +1099,7 @@ fn tick_already_materialized_string_path_records_zero_internal_allocations() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_CONST 0\nSET_GLOBAL 0\nGET_GLOBAL 0\nPOP_N 1\nFRAME_SYNC\nHALT")
|
||||
.expect("assemble");
|
||||
@ -1018,7 +1120,7 @@ fn tick_already_materialized_string_path_records_zero_internal_allocations() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
|
||||
assert!(report.is_none());
|
||||
@ -1093,7 +1195,7 @@ fn tick_composer_bind_scene_operational_error_returns_status_not_crash() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 99\nHOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -1118,7 +1220,7 @@ fn tick_composer_bind_scene_operational_error_returns_status_not_crash() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "operational error must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -1138,7 +1240,7 @@ fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble(
|
||||
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT",
|
||||
@ -1166,7 +1268,7 @@ fn tick_composer_emit_sprite_operational_error_returns_status_not_crash() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "operational error must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -1183,7 +1285,7 @@ fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble(
|
||||
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 4\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT",
|
||||
@ -1211,7 +1313,7 @@ fn tick_composer_emit_sprite_invalid_layer_returns_status_not_crash() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "invalid layer must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -1228,7 +1330,7 @@ fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble(
|
||||
"PUSH_I32 0\nPUSH_I32 64\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT",
|
||||
@ -1247,7 +1349,7 @@ fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() {
|
||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||
let asset_data = test_glyph_asset_data();
|
||||
|
||||
hardware.assets.initialize_for_cartridge(
|
||||
platform.local_hardware().assets.initialize_for_cartridge(
|
||||
vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
|
||||
vec![prometeu_hal::asset::PreloadEntry { asset_id: 7, slot: 0 }],
|
||||
AssetsPayloadSource::from_bytes(asset_data),
|
||||
@ -1263,7 +1365,7 @@ fn tick_composer_emit_sprite_invalid_range_returns_status_not_crash() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "invalid composer parameter range must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -1283,7 +1385,7 @@ fn tick_audio_play_sample_operational_error_returns_status_not_crash() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble(
|
||||
"PUSH_I32 -1\nPUSH_I32 0\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nHOSTCALL 0\nHALT",
|
||||
@ -1311,7 +1413,7 @@ fn tick_audio_play_sample_operational_error_returns_status_not_crash() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "operational error must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -1328,7 +1430,7 @@ fn tick_audio_play_voice_invalid_returns_status_not_crash() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble(
|
||||
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 16\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nPUSH_I32 0\nHOSTCALL 0\nHALT",
|
||||
@ -1356,7 +1458,7 @@ fn tick_audio_play_voice_invalid_returns_status_not_crash() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "invalid voice must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -1373,7 +1475,7 @@ fn tick_audio_play_missing_asset_returns_status_not_crash() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble(
|
||||
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nPUSH_I32 0\nHOSTCALL 0\nHALT",
|
||||
@ -1401,7 +1503,7 @@ fn tick_audio_play_missing_asset_returns_status_not_crash() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "missing audio asset must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -1418,7 +1520,7 @@ fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble(
|
||||
"PUSH_BOOL 1\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 255\nPUSH_I32 128\nPUSH_I32 1\nPUSH_I32 0\nHOSTCALL 0\nHALT",
|
||||
@ -1447,7 +1549,7 @@ fn tick_audio_play_type_mismatch_surfaces_trap_not_panic() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
)
|
||||
.expect("type mismatch must surface as trap");
|
||||
match report {
|
||||
@ -1470,7 +1572,7 @@ fn tick_composer_emit_sprite_type_mismatch_surfaces_trap_not_panic() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble(
|
||||
"PUSH_BOOL 1\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\nHALT",
|
||||
@ -1499,7 +1601,7 @@ fn tick_composer_emit_sprite_type_mismatch_surfaces_trap_not_panic() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
)
|
||||
.expect("type mismatch must surface as trap");
|
||||
match report {
|
||||
|
||||
@ -10,7 +10,7 @@ fn tick_asset_commit_operational_error_returns_status_not_crash() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 999\nHOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -35,7 +35,7 @@ fn tick_asset_commit_operational_error_returns_status_not_crash() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "operational error must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -52,7 +52,7 @@ fn tick_asset_load_missing_asset_returns_status_and_zero_handle() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 999\nPUSH_I32 0\nHOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -77,7 +77,7 @@ fn tick_asset_load_missing_asset_returns_status_and_zero_handle() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "missing asset must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -97,10 +97,10 @@ fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let asset_data = test_glyph_asset_data();
|
||||
hardware.assets.initialize_for_cartridge(
|
||||
platform.local_hardware().assets.initialize_for_cartridge(
|
||||
vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
|
||||
vec![],
|
||||
AssetsPayloadSource::from_bytes(asset_data),
|
||||
@ -128,7 +128,7 @@ fn tick_asset_load_invalid_slot_returns_status_and_zero_handle() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "invalid slot must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -148,7 +148,7 @@ fn tick_asset_status_unknown_handle_returns_status_not_crash() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 999\nHOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -173,7 +173,7 @@ fn tick_asset_status_unknown_handle_returns_status_not_crash() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "unknown asset handle must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -190,10 +190,10 @@ fn tick_bank_info_returns_slot_summary_not_json() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let asset_data = test_glyph_asset_data();
|
||||
hardware.assets.initialize_for_cartridge(
|
||||
platform.local_hardware().assets.initialize_for_cartridge(
|
||||
vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
|
||||
vec![prometeu_hal::asset::PreloadEntry { asset_id: 7, slot: 0 }],
|
||||
AssetsPayloadSource::from_bytes(asset_data),
|
||||
@ -221,7 +221,7 @@ fn tick_bank_info_returns_slot_summary_not_json() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "bank summary must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -261,7 +261,7 @@ fn tick_asset_commit_invalid_transition_returns_status_not_crash() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 1\nHOSTCALL 0\nPOP_N 1\nPUSH_I32 1\nHOSTCALL 1\nHALT")
|
||||
.expect("assemble");
|
||||
@ -287,12 +287,13 @@ fn tick_asset_commit_invalid_transition_returns_status_not_crash() {
|
||||
let cartridge = cartridge_with_program(program, caps::ASSET);
|
||||
|
||||
let asset_data = test_glyph_asset_data();
|
||||
hardware.assets.initialize_for_cartridge(
|
||||
platform.local_hardware().assets.initialize_for_cartridge(
|
||||
vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
|
||||
vec![],
|
||||
AssetsPayloadSource::from_bytes(asset_data),
|
||||
);
|
||||
let handle = hardware.assets.load(7, 0).expect("asset handle must be allocated");
|
||||
let handle =
|
||||
platform.local_hardware().assets.load(7, 0).expect("asset handle must be allocated");
|
||||
|
||||
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||
let report = runtime.tick(
|
||||
@ -304,7 +305,7 @@ fn tick_asset_commit_invalid_transition_returns_status_not_crash() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "invalid transition must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -322,7 +323,7 @@ fn tick_asset_cancel_unknown_handle_returns_status_not_crash() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 999\nHOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -347,7 +348,7 @@ fn tick_asset_cancel_unknown_handle_returns_status_not_crash() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "unknown handle cancel must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -364,7 +365,7 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("PUSH_I32 1\nHOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -380,17 +381,18 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
|
||||
let cartridge = cartridge_with_program(program, caps::ASSET);
|
||||
|
||||
let asset_data = test_glyph_asset_data();
|
||||
hardware.assets.initialize_for_cartridge(
|
||||
platform.local_hardware().assets.initialize_for_cartridge(
|
||||
vec![test_glyph_asset_entry("tile_asset", asset_data.len())],
|
||||
vec![],
|
||||
AssetsPayloadSource::from_bytes(asset_data),
|
||||
);
|
||||
let handle = hardware.assets.load(7, 0).expect("asset handle must be allocated");
|
||||
let handle =
|
||||
platform.local_hardware().assets.load(7, 0).expect("asset handle must be allocated");
|
||||
|
||||
runtime.initialize_vm(&mut log_service, &mut vm, &cartridge).expect("runtime must initialize");
|
||||
|
||||
loop {
|
||||
match hardware.assets.status(handle) {
|
||||
match platform.local_hardware().assets.status(handle) {
|
||||
LoadStatus::READY => break,
|
||||
LoadStatus::PENDING | LoadStatus::LOADING => {
|
||||
std::thread::sleep(std::time::Duration::from_millis(1));
|
||||
@ -399,9 +401,9 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
|
||||
}
|
||||
}
|
||||
|
||||
assert_eq!(hardware.assets.commit(handle), AssetOpStatus::Ok);
|
||||
hardware.assets.apply_commits();
|
||||
assert_eq!(hardware.assets.status(handle), LoadStatus::COMMITTED);
|
||||
assert_eq!(platform.local_hardware().assets.commit(handle), AssetOpStatus::Ok);
|
||||
platform.local_hardware().assets.apply_commits();
|
||||
assert_eq!(platform.local_hardware().assets.status(handle), LoadStatus::COMMITTED);
|
||||
|
||||
let report = runtime.tick(
|
||||
&mut log_service,
|
||||
@ -412,7 +414,7 @@ fn tick_asset_cancel_invalid_transition_returns_status_not_crash() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "cancel after commit must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -429,7 +431,7 @@ fn tick_status_first_surface_smoke_across_composer_audio_and_asset() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble(
|
||||
"PUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 0\nPUSH_BOOL 0\nPUSH_BOOL 0\nPUSH_I32 0\nHOSTCALL 0\n\
|
||||
@ -476,7 +478,7 @@ fn tick_status_first_surface_smoke_across_composer_audio_and_asset() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "mixed status-first surface must not crash");
|
||||
assert!(vm.is_halted());
|
||||
|
||||
@ -11,7 +11,7 @@ fn tick_memcard_slot_roundtrip_for_game_profile() {
|
||||
let mut next_handle = 1;
|
||||
runtime.mount_fs(&mut log_service, &mut fs, &mut fs_state, Box::new(MemFsBackend::default()));
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble(
|
||||
"PUSH_I32 0\nPUSH_I32 0\nPUSH_CONST 0\nHOSTCALL 0\nPOP_N 2\nPUSH_I32 0\nHOSTCALL 1\nPOP_N 1\nPUSH_I32 0\nPUSH_I32 0\nPUSH_I32 10\nHOSTCALL 2\nHALT",
|
||||
@ -66,7 +66,7 @@ fn tick_memcard_slot_roundtrip_for_game_profile() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "memcard roundtrip must not crash");
|
||||
assert!(vm.is_halted());
|
||||
@ -86,7 +86,7 @@ fn tick_memcard_access_is_denied_for_non_game_profile() {
|
||||
let mut open_files: HashMap<u32, String> = HashMap::new();
|
||||
let mut next_handle = 1;
|
||||
let mut vm = VirtualMachine::default();
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble("HOSTCALL 0\nHALT").expect("assemble");
|
||||
let program = serialized_single_function_module(
|
||||
@ -121,7 +121,7 @@ fn tick_memcard_access_is_denied_for_non_game_profile() {
|
||||
&mut next_handle,
|
||||
&mut vm,
|
||||
&signals,
|
||||
&mut hardware,
|
||||
&mut platform,
|
||||
);
|
||||
assert!(report.is_none(), "non-game memcard call must return status");
|
||||
assert!(vm.is_halted());
|
||||
|
||||
@ -9,8 +9,8 @@ use crate::services::memcard::MemcardService;
|
||||
use prometeu_hal::asset::{BankTelemetry, BankType};
|
||||
use prometeu_hal::log::{LogLevel, LogService, LogSource};
|
||||
use prometeu_hal::{
|
||||
FrameId, HardwareBridge, HostContext, InputSignals, RenderSubmission, RenderSubmissionPacket,
|
||||
ShellUiFramePacket,
|
||||
FrameId, HostContext, InputSignals, RenderSubmission, RenderSubmissionPacket,
|
||||
RenderSubmissionSink, RuntimePlatform, ShellUiFramePacket,
|
||||
};
|
||||
use prometeu_vm::LogicalFrameEndingReason;
|
||||
use std::collections::HashMap;
|
||||
@ -18,12 +18,14 @@ use std::panic::{AssertUnwindSafe, catch_unwind};
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
struct HardwareRenderSurface<'a> {
|
||||
hw: &'a mut dyn HardwareBridge,
|
||||
sink: &'a mut dyn RenderSubmissionSink,
|
||||
}
|
||||
|
||||
impl RenderSurface for HardwareRenderSurface<'_> {
|
||||
fn consume_submission(&mut self, submission: &RenderSubmission) {
|
||||
self.hw.publish_render_submission(submission);
|
||||
self.sink
|
||||
.submit_render_submission(submission.clone())
|
||||
.expect("local render submission sink should publish");
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,9 +53,9 @@ impl VirtualMachineRuntime {
|
||||
}
|
||||
|
||||
fn bank_telemetry_summary(
|
||||
hw: &dyn HardwareBridge,
|
||||
platform: &dyn RuntimePlatform,
|
||||
) -> (BankTelemetry, BankTelemetry, BankTelemetry) {
|
||||
let telemetry = hw.assets().bank_telemetry();
|
||||
let telemetry = platform.assets().bank_telemetry();
|
||||
let glyph =
|
||||
telemetry.iter().find(|entry| entry.bank_type == BankType::GLYPH).cloned().unwrap_or(
|
||||
BankTelemetry { bank_type: BankType::GLYPH, used_slots: 0, total_slots: 0 },
|
||||
@ -74,10 +76,10 @@ impl VirtualMachineRuntime {
|
||||
&mut self,
|
||||
log_service: &mut LogService,
|
||||
vm: &mut VirtualMachine,
|
||||
hw: &mut dyn HardwareBridge,
|
||||
platform: &mut dyn RuntimePlatform,
|
||||
) -> Option<CrashReport> {
|
||||
let step_result = {
|
||||
let mut ctx = HostContext::new(Some(hw));
|
||||
let mut ctx = HostContext::new_platform(Some(platform));
|
||||
let mut fs = VirtualFS::new();
|
||||
let mut fs_state = FsState::Unmounted;
|
||||
let mut memcard = MemcardService::new();
|
||||
@ -131,7 +133,7 @@ impl VirtualMachineRuntime {
|
||||
next_handle: &mut u32,
|
||||
vm: &mut VirtualMachine,
|
||||
signals: &InputSignals,
|
||||
hw: &mut dyn HardwareBridge,
|
||||
platform: &mut dyn RuntimePlatform,
|
||||
) -> Option<CrashReport> {
|
||||
let start = Instant::now();
|
||||
self.tick_index += 1;
|
||||
@ -141,7 +143,6 @@ impl VirtualMachineRuntime {
|
||||
}
|
||||
|
||||
self.update_fs(log_service, fs, fs_state);
|
||||
|
||||
if !self.logical_frame_active {
|
||||
if RenderManager::policy_for_app_mode(self.current_cartridge_app_mode)
|
||||
.uses_frame_scheduler()
|
||||
@ -152,7 +153,7 @@ impl VirtualMachineRuntime {
|
||||
}
|
||||
self.logical_frame_active = true;
|
||||
self.logical_frame_remaining_cycles = Self::CYCLES_PER_LOGICAL_FRAME;
|
||||
self.begin_logical_frame(signals, hw);
|
||||
self.begin_logical_frame(signals, platform);
|
||||
|
||||
if self.needs_prepare_entry_call || vm.call_stack_is_empty() {
|
||||
vm.prepare_boot_call();
|
||||
@ -180,7 +181,7 @@ impl VirtualMachineRuntime {
|
||||
|
||||
if budget > 0 {
|
||||
let run_result = {
|
||||
let mut ctx = HostContext::new(Some(hw));
|
||||
let mut ctx = HostContext::new_platform(Some(platform));
|
||||
let mut host = VmRuntimeHost {
|
||||
runtime: self,
|
||||
log_service,
|
||||
@ -248,7 +249,7 @@ impl VirtualMachineRuntime {
|
||||
self.current_app_id,
|
||||
);
|
||||
if self.current_cartridge_app_mode == AppMode::Game {
|
||||
let mut packet = hw.close_game2d_packet();
|
||||
let mut packet = platform.game2d_frame_composer().close_game2d_packet();
|
||||
packet.gfx2d = std::mem::take(&mut self.gfx2d_commands);
|
||||
self.render_manager
|
||||
.close_frame_with_packet(RenderSubmissionPacket::Game2D(packet))
|
||||
@ -262,7 +263,8 @@ impl VirtualMachineRuntime {
|
||||
}
|
||||
|
||||
let render_outcome = catch_unwind(AssertUnwindSafe(|| {
|
||||
let mut surface = HardwareRenderSurface { hw };
|
||||
let mut surface =
|
||||
HardwareRenderSurface { sink: platform.render_submission_sink() };
|
||||
match self
|
||||
.render_manager
|
||||
.active_render_policy()
|
||||
@ -304,7 +306,8 @@ impl VirtualMachineRuntime {
|
||||
}
|
||||
|
||||
// 1. Snapshot full telemetry at logical frame end
|
||||
let (glyph_bank, sound_bank, scene_bank) = Self::bank_telemetry_summary(hw);
|
||||
let (glyph_bank, sound_bank, scene_bank) =
|
||||
Self::bank_telemetry_summary(platform);
|
||||
self.atomic_telemetry
|
||||
.glyph_slots_used
|
||||
.store(glyph_bank.used_slots as u32, Ordering::Relaxed);
|
||||
@ -395,7 +398,7 @@ impl VirtualMachineRuntime {
|
||||
|
||||
// 2. High-frequency telemetry update (only if inspection is active)
|
||||
if self.inspection_active {
|
||||
let (glyph_bank, sound_bank, scene_bank) = Self::bank_telemetry_summary(hw);
|
||||
let (glyph_bank, sound_bank, scene_bank) = Self::bank_telemetry_summary(platform);
|
||||
self.atomic_telemetry
|
||||
.glyph_slots_used
|
||||
.store(glyph_bank.used_slots as u32, Ordering::Relaxed);
|
||||
@ -432,10 +435,10 @@ impl VirtualMachineRuntime {
|
||||
pub(crate) fn begin_logical_frame(
|
||||
&mut self,
|
||||
_signals: &InputSignals,
|
||||
hw: &mut dyn HardwareBridge,
|
||||
platform: &mut dyn RuntimePlatform,
|
||||
) {
|
||||
hw.begin_frame();
|
||||
hw.audio_mut().clear_commands();
|
||||
platform.game2d_frame_composer().begin_frame();
|
||||
platform.audio_mut().clear_commands();
|
||||
self.logs_written_this_frame.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1350,8 +1350,9 @@ fn input_touch_x(
|
||||
ctx: &mut HostContext<'_>,
|
||||
) -> Result<Vec<Value>, IntrinsicExecutionError> {
|
||||
expect_touch_handle(args, "x")?;
|
||||
let hw = ctx.require_hw().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?;
|
||||
Ok(vec![Value::Int64(hw.touch().x() as i64)])
|
||||
let platform =
|
||||
ctx.require_platform().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?;
|
||||
Ok(vec![Value::Int64(platform.input().touch().x() as i64)])
|
||||
}
|
||||
|
||||
fn input_touch_y(
|
||||
@ -1359,8 +1360,9 @@ fn input_touch_y(
|
||||
ctx: &mut HostContext<'_>,
|
||||
) -> Result<Vec<Value>, IntrinsicExecutionError> {
|
||||
expect_touch_handle(args, "y")?;
|
||||
let hw = ctx.require_hw().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?;
|
||||
Ok(vec![Value::Int64(hw.touch().y() as i64)])
|
||||
let platform =
|
||||
ctx.require_platform().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?;
|
||||
Ok(vec![Value::Int64(platform.input().touch().y() as i64)])
|
||||
}
|
||||
|
||||
fn input_button_pressed(
|
||||
@ -1435,9 +1437,11 @@ fn resolve_button(
|
||||
ctx: &mut HostContext<'_>,
|
||||
) -> Result<prometeu_hal::button::Button, IntrinsicExecutionError> {
|
||||
let carrier = expect_builtin_carrier(args, 0)?;
|
||||
let hw = ctx.require_hw().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?;
|
||||
let platform =
|
||||
ctx.require_platform().map_err(|_| IntrinsicExecutionError::HardwareUnavailable)?;
|
||||
let input = platform.input();
|
||||
if carrier == TOUCH_BUTTON_CARRIER {
|
||||
return Ok(*hw.touch().f());
|
||||
return Ok(*input.touch().f());
|
||||
}
|
||||
|
||||
let Some(index) = carrier.checked_sub(PAD_BUTTON_BASE) else {
|
||||
@ -1448,18 +1452,18 @@ fn resolve_button(
|
||||
});
|
||||
};
|
||||
let button = match index {
|
||||
0 => *hw.pad().up(),
|
||||
1 => *hw.pad().down(),
|
||||
2 => *hw.pad().left(),
|
||||
3 => *hw.pad().right(),
|
||||
4 => *hw.pad().a(),
|
||||
5 => *hw.pad().b(),
|
||||
6 => *hw.pad().x(),
|
||||
7 => *hw.pad().y(),
|
||||
8 => *hw.pad().l(),
|
||||
9 => *hw.pad().r(),
|
||||
10 => *hw.pad().start(),
|
||||
11 => *hw.pad().select(),
|
||||
0 => *input.pad().up(),
|
||||
1 => *input.pad().down(),
|
||||
2 => *input.pad().left(),
|
||||
3 => *input.pad().right(),
|
||||
4 => *input.pad().a(),
|
||||
5 => *input.pad().b(),
|
||||
6 => *input.pad().x(),
|
||||
7 => *input.pad().y(),
|
||||
8 => *input.pad().l(),
|
||||
9 => *input.pad().r(),
|
||||
10 => *input.pad().start(),
|
||||
11 => *input.pad().select(),
|
||||
_ => {
|
||||
return Err(IntrinsicExecutionError::InvalidBuiltinCarrier {
|
||||
owner: "input.button",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use prometeu_drivers::hardware::Hardware;
|
||||
use prometeu_firmware::{BootTarget, Firmware};
|
||||
use prometeu_hal::RuntimePlatform;
|
||||
use prometeu_hal::cartridge_loader::CartridgeLoader;
|
||||
use prometeu_hal::debugger_protocol::*;
|
||||
use prometeu_hal::telemetry::{CertificationConfig, TelemetryFrame};
|
||||
@ -99,7 +99,7 @@ impl HostDebugger {
|
||||
|
||||
/// Main maintenance method called by the HostRunner every iteration.
|
||||
/// It handles new connections and processes incoming commands.
|
||||
pub fn check_commands(&mut self, firmware: &mut Firmware, hardware: &mut Hardware) {
|
||||
pub fn check_commands(&mut self, firmware: &mut Firmware, platform: &mut dyn RuntimePlatform) {
|
||||
// 1. Accept new client connections.
|
||||
if let Some(listener) = &self.listener
|
||||
&& let Ok((stream, _addr)) = listener.accept()
|
||||
@ -154,7 +154,7 @@ impl HostDebugger {
|
||||
continue;
|
||||
}
|
||||
if let Ok(cmd) = serde_json::from_str::<DebugCommand>(trimmed) {
|
||||
self.handle_command(cmd, firmware, hardware);
|
||||
self.handle_command(cmd, firmware, platform);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -182,7 +182,7 @@ impl HostDebugger {
|
||||
&mut self,
|
||||
cmd: DebugCommand,
|
||||
firmware: &mut Firmware,
|
||||
hardware: &mut Hardware,
|
||||
platform: &mut dyn RuntimePlatform,
|
||||
) {
|
||||
match cmd {
|
||||
DebugCommand::Ok | DebugCommand::Start => {
|
||||
@ -201,7 +201,7 @@ impl HostDebugger {
|
||||
DebugCommand::Step => {
|
||||
// Execute exactly one instruction and keep paused.
|
||||
firmware.os.vm().set_paused(true);
|
||||
let _ = firmware.os.vm().debug_step_instruction(&mut firmware.vm, hardware);
|
||||
let _ = firmware.os.vm().debug_step_instruction(&mut firmware.vm, platform);
|
||||
}
|
||||
DebugCommand::StepFrame => {
|
||||
// Execute until the end of the current logical frame.
|
||||
@ -372,7 +372,7 @@ impl HostDebugger {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use prometeu_drivers::hardware::Hardware;
|
||||
use prometeu_drivers::TestPlatform;
|
||||
use prometeu_hal::debugger_protocol::DebugCommand;
|
||||
|
||||
#[test]
|
||||
@ -396,15 +396,19 @@ mod tests {
|
||||
fn handle_command_updates_pause_and_step_flags_without_host_io() {
|
||||
let mut debugger = HostDebugger::new();
|
||||
let mut firmware = Firmware::new(None);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
|
||||
debugger.handle_command(DebugCommand::Pause, &mut firmware, &mut hardware);
|
||||
debugger.handle_command(DebugCommand::Pause, &mut firmware, platform.local_hardware_mut());
|
||||
assert!(firmware.os.vm().paused());
|
||||
|
||||
debugger.handle_command(DebugCommand::Resume, &mut firmware, &mut hardware);
|
||||
debugger.handle_command(DebugCommand::Resume, &mut firmware, platform.local_hardware_mut());
|
||||
assert!(!firmware.os.vm().paused());
|
||||
|
||||
debugger.handle_command(DebugCommand::StepFrame, &mut firmware, &mut hardware);
|
||||
debugger.handle_command(
|
||||
DebugCommand::StepFrame,
|
||||
&mut firmware,
|
||||
platform.local_hardware_mut(),
|
||||
);
|
||||
assert!(!firmware.os.vm().paused());
|
||||
assert!(firmware.os.vm().debug_step_requested());
|
||||
}
|
||||
@ -413,12 +417,12 @@ mod tests {
|
||||
fn handle_command_start_leaves_waiting_for_start_mode() {
|
||||
let mut debugger = HostDebugger::new();
|
||||
let mut firmware = Firmware::new(None);
|
||||
let mut hardware = Hardware::new();
|
||||
let mut platform = TestPlatform::new();
|
||||
|
||||
debugger.waiting_for_start = true;
|
||||
firmware.os.vm().set_paused(true);
|
||||
|
||||
debugger.handle_command(DebugCommand::Start, &mut firmware, &mut hardware);
|
||||
debugger.handle_command(DebugCommand::Start, &mut firmware, platform.local_hardware_mut());
|
||||
|
||||
assert!(!debugger.waiting_for_start);
|
||||
assert!(!firmware.os.vm().paused());
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
use prometeu_drivers::hardware::Hardware;
|
||||
use prometeu_hal::InputSignals;
|
||||
use winit::event::{ElementState, MouseButton, WindowEvent};
|
||||
use winit::keyboard::{KeyCode, PhysicalKey};
|
||||
@ -14,7 +13,7 @@ struct FbViewport {
|
||||
}
|
||||
|
||||
impl FbViewport {
|
||||
fn framebuffer_viewport(window: &Window) -> Option<FbViewport> {
|
||||
fn framebuffer_viewport(window: &Window, display_size: (usize, usize)) -> Option<FbViewport> {
|
||||
let size = window.inner_size();
|
||||
|
||||
let win_w = size.width as f32;
|
||||
@ -24,8 +23,8 @@ impl FbViewport {
|
||||
return None;
|
||||
}
|
||||
|
||||
let fb_w = Hardware::W as f32;
|
||||
let fb_h = Hardware::H as f32;
|
||||
let fb_w = display_size.0 as f32;
|
||||
let fb_h = display_size.1 as f32;
|
||||
|
||||
let scale_x = (win_w / fb_w).floor();
|
||||
let scale_y = (win_h / fb_h).floor();
|
||||
@ -44,17 +43,18 @@ impl FbViewport {
|
||||
|
||||
pub struct HostInputHandler {
|
||||
pub signals: InputSignals,
|
||||
display_size: (usize, usize),
|
||||
}
|
||||
|
||||
impl Default for HostInputHandler {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
Self::new((480, 270))
|
||||
}
|
||||
}
|
||||
|
||||
impl HostInputHandler {
|
||||
pub fn new() -> Self {
|
||||
Self { signals: InputSignals::default() }
|
||||
pub fn new(display_size: (usize, usize)) -> Self {
|
||||
Self { signals: InputSignals::default(), display_size }
|
||||
}
|
||||
|
||||
pub fn handle_event(&mut self, event: &WindowEvent, window: &Window) {
|
||||
@ -87,7 +87,9 @@ impl HostInputHandler {
|
||||
}
|
||||
|
||||
WindowEvent::CursorMoved { position, .. } => {
|
||||
if let Some((x, y)) = window_to_fb(position.x as f32, position.y as f32, window) {
|
||||
if let Some((x, y)) =
|
||||
window_to_fb(position.x as f32, position.y as f32, window, self.display_size)
|
||||
{
|
||||
self.signals.x_pos = x;
|
||||
self.signals.y_pos = y;
|
||||
}
|
||||
@ -107,8 +109,13 @@ impl HostInputHandler {
|
||||
_ => {}
|
||||
}
|
||||
|
||||
fn window_to_fb(wx: f32, wy: f32, window: &Window) -> Option<(i32, i32)> {
|
||||
let viewport = FbViewport::framebuffer_viewport(window)?;
|
||||
fn window_to_fb(
|
||||
wx: f32,
|
||||
wy: f32,
|
||||
window: &Window,
|
||||
display_size: (usize, usize),
|
||||
) -> Option<(i32, i32)> {
|
||||
let viewport = FbViewport::framebuffer_viewport(window, display_size)?;
|
||||
|
||||
let local_x = wx - viewport.x;
|
||||
let local_y = wy - viewport.y;
|
||||
@ -120,7 +127,10 @@ impl HostInputHandler {
|
||||
let fb_x = (local_x / viewport.scale).floor() as i32;
|
||||
let fb_y = (local_y / viewport.scale).floor() as i32;
|
||||
|
||||
Some((fb_x.clamp(0, Hardware::W as i32 - 1), fb_y.clamp(0, Hardware::H as i32 - 1)))
|
||||
Some((
|
||||
fb_x.clamp(0, display_size.0 as i32 - 1),
|
||||
fb_y.clamp(0, display_size.1 as i32 - 1),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ use pixels::{Pixels, PixelsBuilder, SurfaceTexture};
|
||||
use prometeu_drivers::AudioCommand;
|
||||
use prometeu_drivers::hardware::Hardware;
|
||||
use prometeu_firmware::{BootTarget, Firmware, FirmwareState};
|
||||
use prometeu_hal::RuntimePlatform;
|
||||
use prometeu_hal::telemetry::CertificationConfig;
|
||||
use std::time::{Duration, Instant};
|
||||
use winit::application::ApplicationHandler;
|
||||
@ -152,12 +153,15 @@ impl HostRunner {
|
||||
firmware.os.fs().mount(Box::new(backend));
|
||||
}
|
||||
|
||||
let hardware = Hardware::new();
|
||||
let display_size = hardware.display_size();
|
||||
|
||||
Self {
|
||||
window: None,
|
||||
pixels: None,
|
||||
hardware: Hardware::new(),
|
||||
hardware,
|
||||
firmware,
|
||||
input: HostInputHandler::new(),
|
||||
input: HostInputHandler::new(display_size),
|
||||
fs_root,
|
||||
log_sink: HostConsoleSink::new(),
|
||||
frame_target_dt: Duration::from_nanos(1_000_000_000 / target_fps),
|
||||
@ -228,11 +232,11 @@ impl ApplicationHandler for HostRunner {
|
||||
let size = window.inner_size();
|
||||
let surface_texture = SurfaceTexture::new(size.width, size.height, window);
|
||||
|
||||
let mut pixels =
|
||||
PixelsBuilder::new(Hardware::W as u32, Hardware::H as u32, surface_texture)
|
||||
.present_mode(PresentMode::Fifo) // activate vsync
|
||||
.build()
|
||||
.expect("failed to create Pixels");
|
||||
let (display_w, display_h) = self.hardware.display_size();
|
||||
let mut pixels = PixelsBuilder::new(display_w as u32, display_h as u32, surface_texture)
|
||||
.present_mode(PresentMode::Fifo) // activate vsync
|
||||
.build()
|
||||
.expect("failed to create Pixels");
|
||||
|
||||
pixels.frame_mut().fill(0);
|
||||
|
||||
@ -381,7 +385,7 @@ impl ApplicationHandler for HostRunner {
|
||||
self.audio.update_stats(&mut self.stats);
|
||||
|
||||
// Update technical statistics displayed in the window title.
|
||||
self.stats.update(now, self.window, &self.hardware, &mut self.firmware);
|
||||
self.stats.update(now, self.window, &mut self.firmware);
|
||||
|
||||
// Synchronize system logs to the host console.
|
||||
let last_seq = self.log_sink.last_seq().unwrap_or(u64::MAX);
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
use prometeu_drivers::hardware::Hardware;
|
||||
use prometeu_firmware::Firmware;
|
||||
use std::time::{Duration, Instant};
|
||||
use winit::window::Window;
|
||||
@ -60,13 +59,7 @@ impl HostStats {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
now: Instant,
|
||||
window: Option<&Window>,
|
||||
_hardware: &Hardware,
|
||||
firmware: &mut Firmware,
|
||||
) {
|
||||
pub fn update(&mut self, now: Instant, window: Option<&Window>, firmware: &mut Firmware) {
|
||||
let stats_elapsed = now.duration_since(self.last_stats_update);
|
||||
if stats_elapsed >= Duration::from_secs(1) {
|
||||
self.current_fps = self.frames_since_last_update as f64 / stats_elapsed.as_secs_f64();
|
||||
|
||||
@ -31,6 +31,13 @@ MUST contain a frame id, the active app mode, and one typed packet:
|
||||
immutable. The runtime backpressure policy is latest-complete-submission-wins;
|
||||
it MUST NOT accumulate an unbounded frame queue.
|
||||
|
||||
The platform layer is the runtime-facing implementation boundary for render
|
||||
handoff. Runtime and firmware code publish completed frames through typed
|
||||
platform services such as `RenderSubmissionSink`, and they access Game 2D
|
||||
composition through `Game2DFrameComposer`. They MUST NOT depend on a monolithic
|
||||
hardware bridge, mutable `Hardware`, mutable `Gfx`, or live `FrameComposer`
|
||||
reference as the render handoff contract.
|
||||
|
||||
The current 2D graphics model is based on:
|
||||
|
||||
- framebuffer
|
||||
@ -127,6 +134,10 @@ to the logical/runtime side before handoff, or to an owning service. They MUST
|
||||
NOT require the render consumer to hold mutable VM, `Hardware`, `Gfx`, or
|
||||
`FrameComposer` references.
|
||||
|
||||
Local host implementations may keep a concrete hardware object internally as a
|
||||
platform implementation detail. That object is not part of the runtime-facing
|
||||
render boundary; the boundary is the typed platform service set.
|
||||
|
||||
The runtime does not guarantee visual integrity if a developer or framework
|
||||
replaces resources behind an in-flight submission in a way that violates asset
|
||||
discipline.
|
||||
|
||||
@ -123,9 +123,13 @@ The graphics system:
|
||||
|
||||
The platform layer:
|
||||
|
||||
- exposes typed service facades for render submission, render backend
|
||||
execution, Game 2D composition, input, audio, assets, and telemetry
|
||||
- consumes closed render submissions through a render-surface implementation
|
||||
- transports published RGBA8888 output into a host presentation surface without
|
||||
injecting host-owned debug overlay pixels
|
||||
- is the runtime-facing portability boundary; runtime and firmware code must not
|
||||
depend on a monolithic hardware bridge or on a concrete hardware aggregate
|
||||
|
||||
The host presentation layer MUST treat RGBA8888 as the canonical logical
|
||||
framebuffer format. RGB565 conversion is not part of the normal host
|
||||
|
||||
@ -50,6 +50,12 @@ Does not belong in `docs/runtime/specs/` as the primary canonical source:
|
||||
|
||||
Here, hardware is not a generic bucket for everything. In the current package, "hardware" means the machine's virtual peripherals: GFX, AUDIO, INPUT, TOUCH, and MEMCARD/save memory. VM, firmware, cartridge, and ABI sit in sibling categories rather than inside hardware.
|
||||
|
||||
The implementation boundary for host portability is the platform layer. Runtime,
|
||||
firmware, and host integration code use typed platform services for rendering,
|
||||
input, audio, assets, and telemetry. A concrete local hardware aggregate may
|
||||
exist inside a host or test platform, but it is not the normative runtime-facing
|
||||
contract.
|
||||
|
||||
## Document Functions
|
||||
|
||||
- `normative`: defines the technical contract, expected behavior, or implementation-facing surface.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user