diff --git a/crates/console/prometeu-drivers/src/frame_composer.rs b/crates/console/prometeu-drivers/src/frame_composer.rs index dd95bfda..80e3bd5a 100644 --- a/crates/console/prometeu-drivers/src/frame_composer.rs +++ b/crates/console/prometeu-drivers/src/frame_composer.rs @@ -8,8 +8,8 @@ use prometeu_hal::scene_viewport_resolver::{ }; use prometeu_hal::sprite::Sprite; use prometeu_hal::{ - BoundScenePacket, Camera2D, ComposerFramePacket, Game2DFramePacket, GameSpritePacket, - GfxBridge, HudPacket, + BoundScenePacket, Camera2D, ComposerFramePacket, ComposerOpStatus, Game2DFrameComposer, + Game2DFramePacket, GameSpritePacket, GfxBridge, HudPacket, }; use std::sync::Arc; @@ -452,6 +452,41 @@ impl FrameComposer { } } +impl Game2DFrameComposer for FrameComposer { + fn begin_frame(&mut self) { + FrameComposer::begin_frame(self); + } + + fn bind_scene(&mut self, scene_bank_id: usize) -> ComposerOpStatus { + if FrameComposer::bind_scene(self, scene_bank_id) { + ComposerOpStatus::Ok + } else { + ComposerOpStatus::SceneUnavailable + } + } + + fn unbind_scene(&mut self) { + FrameComposer::unbind_scene(self); + } + + fn set_camera(&mut self, x: i32, y: i32) -> ComposerOpStatus { + FrameComposer::set_camera(self, x, y); + ComposerOpStatus::Ok + } + + fn emit_sprite(&mut self, sprite: Sprite) -> ComposerOpStatus { + if FrameComposer::emit_sprite(self, sprite) { + ComposerOpStatus::Ok + } else { + ComposerOpStatus::SpriteOverflow + } + } + + fn close_game2d_packet(&self) -> Game2DFramePacket { + FrameComposer::close_game2d_packet(self) + } +} + #[cfg(test)] mod tests { use super::*; @@ -896,6 +931,45 @@ mod tests { assert!(packet.gfx2d.is_empty()); } + #[test] + fn game2d_frame_composer_facade_closes_equivalent_packet() { + let mut frame_composer = FrameComposer::new( + Hardware::W, + Hardware::H, + Arc::new(MemoryBanks::default()), + make_glyph_slot_index(&[]), + ); + + let composer: &mut dyn Game2DFrameComposer = &mut frame_composer; + assert_eq!(composer.set_camera(10, 20), ComposerOpStatus::Ok); + composer.begin_frame(); + assert_eq!( + composer.emit_sprite(Sprite { + glyph: Glyph { glyph_id: 7, palette_id: 3 }, + x: 4, + y: 5, + layer: 2, + bank_id: 1, + active: false, + flip_x: true, + flip_y: false, + priority: 9, + }), + ComposerOpStatus::Ok + ); + + let packet = composer.close_game2d_packet(); + + assert_eq!(packet.composer.bound_scene, None); + assert_eq!(packet.composer.camera, Camera2D { x: 10, y: 20 }); + assert_eq!(packet.composer.sprites.len(), 1); + assert_eq!(packet.composer.sprites[0].glyph_id, 7); + assert_eq!(packet.composer.sprites[0].palette_id, 3); + assert_eq!(packet.composer.sprites[0].layer, 2); + assert!(packet.composer.sprites[0].flip_x); + assert!(packet.gfx2d.is_empty()); + } + #[test] fn composer_buffer_preserves_integer_layer_ordering() { let mut frame_composer = FrameComposer::new( diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index 3eba94ea..63e9b2be 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -11,7 +11,9 @@ use crate::touch::Touch; use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::sprite::Sprite; use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge}; -use prometeu_hal::{Game2DFramePacket, RenderSubmission, RenderSubmissionPacket}; +use prometeu_hal::{ + Game2DFrameComposer, Game2DFramePacket, RenderSubmission, RenderSubmissionPacket, +}; use prometeu_hal::{RenderSubmissionSink, RenderSubmitError}; use std::sync::Arc; @@ -149,6 +151,45 @@ impl RenderSubmissionSink for Hardware { } } +impl Game2DFrameComposer for Hardware { + fn begin_frame(&mut self) { + self.frame_composer.begin_frame(); + } + + fn bind_scene(&mut self, scene_bank_id: usize) -> prometeu_hal::ComposerOpStatus { + if self.frame_composer.bind_scene(scene_bank_id) { + prometeu_hal::ComposerOpStatus::Ok + } else { + prometeu_hal::ComposerOpStatus::SceneUnavailable + } + } + + fn unbind_scene(&mut self) { + self.frame_composer.unbind_scene(); + } + + fn set_camera(&mut self, x: i32, y: i32) -> prometeu_hal::ComposerOpStatus { + self.frame_composer.set_camera(x, y); + prometeu_hal::ComposerOpStatus::Ok + } + + fn emit_sprite(&mut self, sprite: Sprite) -> prometeu_hal::ComposerOpStatus { + if !self.has_glyph_bank(sprite.bank_id as usize) { + 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 Hardware { /// Internal hardware width in pixels. pub const W: usize = 480; @@ -294,9 +335,9 @@ mod tests { let mut glyph_slots = [None; 16]; glyph_slots[0] = Some(0); hardware.assets.glyph_asset_slot_index().rebuild_from_slots(&glyph_slots); - assert!(hardware.bind_scene(0)); + assert!(HardwareBridge::bind_scene(&mut hardware, 0)); - let mut packet = hardware.close_game2d_packet(); + let mut packet = HardwareBridge::close_game2d_packet(&hardware); packet.gfx2d = vec![Gfx2dCommand::FillRect { rect: Rect { x: 0, y: 0, w: 1, h: 1 }, color: Color::BLUE, @@ -321,7 +362,7 @@ mod tests { let mut glyph_slots = [None; 16]; glyph_slots[0] = Some(0); hardware.assets.glyph_asset_slot_index().rebuild_from_slots(&glyph_slots); - assert!(hardware.bind_scene(0)); + assert!(HardwareBridge::bind_scene(&mut hardware, 0)); let packet = Game2DFramePacket::new( ComposerFramePacket { @@ -343,7 +384,7 @@ mod tests { Vec::new(), ); - hardware.begin_frame(); + HardwareBridge::begin_frame(&mut hardware); RenderSubmissionSink::submit_render_submission( &mut hardware, RenderSubmission::game2d(FrameId::ZERO, packet), diff --git a/crates/console/prometeu-hal/src/lib.rs b/crates/console/prometeu-hal/src/lib.rs index 59c60edd..517b8099 100644 --- a/crates/console/prometeu-hal/src/lib.rs +++ b/crates/console/prometeu-hal/src/lib.rs @@ -49,8 +49,8 @@ pub use native_interface::{NativeInterface, SyscallId}; pub use pad_bridge::PadBridge; pub use platform::{ AssetStoragePlatform, AudioPlatform, ClockPacingPlatform, Game2DFrameComposer, InputPlatform, - LegacyHardwareRenderSubmissionSink, RenderBackend, RenderSubmissionSink, RenderSubmitError, - RuntimePlatform, TelemetryPlatform, TestPlatform, + LegacyHardwareGame2DFrameComposer, LegacyHardwareRenderSubmissionSink, RenderBackend, + RenderSubmissionSink, RenderSubmitError, RuntimePlatform, TelemetryPlatform, TestPlatform, }; pub use render_submission::{ BoundScenePacket, Camera2D, ComposerFramePacket, FrameId, Game2DFramePacket, GameSpritePacket, diff --git a/crates/console/prometeu-hal/src/platform.rs b/crates/console/prometeu-hal/src/platform.rs index 7d448fda..57b1d1a9 100644 --- a/crates/console/prometeu-hal/src/platform.rs +++ b/crates/console/prometeu-hal/src/platform.rs @@ -52,7 +52,55 @@ pub trait Game2DFrameComposer { fn set_camera(&mut self, x: i32, y: i32) -> ComposerOpStatus; fn emit_sprite(&mut self, sprite: Sprite) -> ComposerOpStatus; fn close_game2d_packet(&self) -> Game2DFramePacket; - fn has_glyph_bank(&self, bank_id: usize) -> bool; +} + +pub struct LegacyHardwareGame2DFrameComposer<'a> { + hw: &'a mut dyn HardwareBridge, +} + +impl<'a> LegacyHardwareGame2DFrameComposer<'a> { + pub fn new(hw: &'a mut dyn HardwareBridge) -> Self { + Self { hw } + } +} + +impl Game2DFrameComposer for LegacyHardwareGame2DFrameComposer<'_> { + fn begin_frame(&mut self) { + self.hw.begin_frame(); + } + + fn bind_scene(&mut self, scene_bank_id: usize) -> ComposerOpStatus { + if self.hw.bind_scene(scene_bank_id) { + ComposerOpStatus::Ok + } else { + ComposerOpStatus::SceneUnavailable + } + } + + fn unbind_scene(&mut self) { + self.hw.unbind_scene(); + } + + fn set_camera(&mut self, x: i32, y: i32) -> ComposerOpStatus { + self.hw.set_camera(x, y); + ComposerOpStatus::Ok + } + + fn emit_sprite(&mut self, sprite: Sprite) -> ComposerOpStatus { + if !self.hw.has_glyph_bank(sprite.bank_id as usize) { + return ComposerOpStatus::BankInvalid; + } + + if self.hw.emit_sprite(sprite) { + ComposerOpStatus::Ok + } else { + ComposerOpStatus::SpriteOverflow + } + } + + fn close_game2d_packet(&self) -> Game2DFramePacket { + self.hw.close_game2d_packet() + } } pub trait AudioPlatform: AudioBridge {} @@ -135,10 +183,6 @@ mod tests { fn close_game2d_packet(&self) -> Game2DFramePacket { Game2DFramePacket::default() } - - fn has_glyph_bank(&self, _bank_id: usize) -> bool { - false - } } #[test] diff --git a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs index 1425f4fd..0106a653 100644 --- a/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/services/vm_runtime/dispatch.rs @@ -12,8 +12,9 @@ use prometeu_hal::sprite::Sprite; use prometeu_hal::syscalls::Syscall; use prometeu_hal::vm_fault::VmFault; use prometeu_hal::{ - AudioOpStatus, ComposerOpStatus, Gfx2dCommand, GfxUiCommand, HostContext, HostReturn, - NativeInterface, SyscallId, expect_bool, expect_int, + AudioOpStatus, ComposerOpStatus, Game2DFrameComposer, Gfx2dCommand, GfxUiCommand, HostContext, + HostReturn, LegacyHardwareGame2DFrameComposer, NativeInterface, SyscallId, expect_bool, + expect_int, }; use std::collections::HashMap; use std::ops::{Deref, DerefMut}; @@ -385,23 +386,24 @@ impl NativeInterface for VmRuntimeHost<'_> { } }; - let bound = catch_unwind(AssertUnwindSafe(|| hw.bind_scene(scene_bank_id))) - .map_err(|payload| VmFault::Panic(Self::panic_payload_to_string(payload)))?; + let status = catch_unwind(AssertUnwindSafe(|| { + let mut composer = LegacyHardwareGame2DFrameComposer::new(hw); + composer.bind_scene(scene_bank_id) + })) + .map_err(|payload| VmFault::Panic(Self::panic_payload_to_string(payload)))?; - let status = - if bound { ComposerOpStatus::Ok } else { ComposerOpStatus::SceneUnavailable }; ret.push_int(status as i64); Ok(()) } Syscall::ComposerUnbindScene => { - hw.unbind_scene(); + LegacyHardwareGame2DFrameComposer::new(hw).unbind_scene(); ret.push_int(ComposerOpStatus::Ok as i64); Ok(()) } Syscall::ComposerSetCamera => { let x = Self::int_arg_to_i32_trap(expect_int(args, 0)?, "camera x")?; let y = Self::int_arg_to_i32_trap(expect_int(args, 1)?, "camera y")?; - hw.set_camera(x, y); + LegacyHardwareGame2DFrameComposer::new(hw).set_camera(x, y); Ok(()) } Syscall::ComposerEmitSprite => { @@ -449,12 +451,7 @@ impl NativeInterface for VmRuntimeHost<'_> { } }; - if !hw.has_glyph_bank(bank_id as usize) { - ret.push_int(ComposerOpStatus::BankInvalid as i64); - return Ok(()); - } - - let emitted = hw.emit_sprite(Sprite { + let status = LegacyHardwareGame2DFrameComposer::new(hw).emit_sprite(Sprite { glyph: Glyph { glyph_id, palette_id }, x, y, @@ -465,8 +462,6 @@ impl NativeInterface for VmRuntimeHost<'_> { flip_y, priority, }); - let status = - if emitted { ComposerOpStatus::Ok } else { ComposerOpStatus::SpriteOverflow }; ret.push_int(status as i64); Ok(()) }