implements PLN-0102

This commit is contained in:
bQUARKz 2026-06-15 05:51:07 +01:00
parent 6f3df74e79
commit d2bace2612
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
5 changed files with 184 additions and 30 deletions

View File

@ -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(

View File

@ -11,7 +11,9 @@ 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::{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 prometeu_hal::{RenderSubmissionSink, RenderSubmitError};
use std::sync::Arc; 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 { impl Hardware {
/// Internal hardware width in pixels. /// Internal hardware width in pixels.
pub const W: usize = 480; pub const W: usize = 480;
@ -294,9 +335,9 @@ 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!(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 { 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,
@ -321,7 +362,7 @@ 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!(HardwareBridge::bind_scene(&mut hardware, 0));
let packet = Game2DFramePacket::new( let packet = Game2DFramePacket::new(
ComposerFramePacket { ComposerFramePacket {
@ -343,7 +384,7 @@ mod tests {
Vec::new(), Vec::new(),
); );
hardware.begin_frame(); HardwareBridge::begin_frame(&mut hardware);
RenderSubmissionSink::submit_render_submission( RenderSubmissionSink::submit_render_submission(
&mut hardware, &mut hardware,
RenderSubmission::game2d(FrameId::ZERO, packet), RenderSubmission::game2d(FrameId::ZERO, packet),

View File

@ -49,8 +49,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,
LegacyHardwareRenderSubmissionSink, RenderBackend, RenderSubmissionSink, RenderSubmitError, LegacyHardwareGame2DFrameComposer, LegacyHardwareRenderSubmissionSink, RenderBackend,
RuntimePlatform, TelemetryPlatform, TestPlatform, RenderSubmissionSink, RenderSubmitError, RuntimePlatform, TelemetryPlatform, TestPlatform,
}; };
pub use render_submission::{ pub use render_submission::{
BoundScenePacket, Camera2D, ComposerFramePacket, FrameId, Game2DFramePacket, GameSpritePacket, BoundScenePacket, Camera2D, ComposerFramePacket, FrameId, Game2DFramePacket, GameSpritePacket,

View File

@ -52,7 +52,55 @@ 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 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 {} pub trait AudioPlatform: AudioBridge {}
@ -135,10 +183,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]

View File

@ -12,8 +12,9 @@ use prometeu_hal::sprite::Sprite;
use prometeu_hal::syscalls::Syscall; use prometeu_hal::syscalls::Syscall;
use prometeu_hal::vm_fault::VmFault; use prometeu_hal::vm_fault::VmFault;
use prometeu_hal::{ use prometeu_hal::{
AudioOpStatus, ComposerOpStatus, Gfx2dCommand, GfxUiCommand, HostContext, HostReturn, AudioOpStatus, ComposerOpStatus, Game2DFrameComposer, Gfx2dCommand, GfxUiCommand, HostContext,
NativeInterface, SyscallId, expect_bool, expect_int, HostReturn, LegacyHardwareGame2DFrameComposer, NativeInterface, SyscallId, expect_bool,
expect_int,
}; };
use std::collections::HashMap; use std::collections::HashMap;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
@ -385,23 +386,24 @@ 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)))?; 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); ret.push_int(status as i64);
Ok(()) Ok(())
} }
Syscall::ComposerUnbindScene => { Syscall::ComposerUnbindScene => {
hw.unbind_scene(); LegacyHardwareGame2DFrameComposer::new(hw).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); LegacyHardwareGame2DFrameComposer::new(hw).set_camera(x, y);
Ok(()) Ok(())
} }
Syscall::ComposerEmitSprite => { Syscall::ComposerEmitSprite => {
@ -449,12 +451,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
} }
}; };
if !hw.has_glyph_bank(bank_id as usize) { let status = LegacyHardwareGame2DFrameComposer::new(hw).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,
@ -465,8 +462,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(())
} }