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