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::{
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(

View File

@ -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),

View File

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

View File

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

View File

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