242 lines
6.9 KiB
Rust
242 lines
6.9 KiB
Rust
use crate::app_mode::AppMode;
|
|
use crate::color::Color;
|
|
use crate::primitives::Rect;
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
|
pub struct FrameId(pub u64);
|
|
|
|
impl FrameId {
|
|
pub const ZERO: Self = Self(0);
|
|
|
|
pub const fn new(value: u64) -> Self {
|
|
Self(value)
|
|
}
|
|
|
|
pub const fn get(self) -> u64 {
|
|
self.0
|
|
}
|
|
|
|
pub const fn next(self) -> Self {
|
|
Self(self.0.wrapping_add(1))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct RenderSubmission {
|
|
pub frame_id: FrameId,
|
|
pub app_mode: AppMode,
|
|
pub ownership: RenderOwnership,
|
|
pub packet: RenderSubmissionPacket,
|
|
}
|
|
|
|
impl RenderSubmission {
|
|
pub const fn game2d(frame_id: FrameId, packet: Game2DFramePacket) -> Self {
|
|
Self {
|
|
frame_id,
|
|
app_mode: AppMode::Game,
|
|
ownership: RenderOwnership::new(0, AppMode::Game, 0),
|
|
packet: RenderSubmissionPacket::Game2D(packet),
|
|
}
|
|
}
|
|
|
|
pub const fn shell_ui(frame_id: FrameId, packet: ShellUiFramePacket) -> Self {
|
|
Self {
|
|
frame_id,
|
|
app_mode: AppMode::Shell,
|
|
ownership: RenderOwnership::new(0, AppMode::Shell, 0),
|
|
packet: RenderSubmissionPacket::ShellUi(packet),
|
|
}
|
|
}
|
|
|
|
pub const fn with_ownership(mut self, ownership: RenderOwnership) -> Self {
|
|
self.ownership = ownership;
|
|
self
|
|
}
|
|
|
|
pub const fn is_coherent(&self) -> bool {
|
|
matches!(
|
|
(self.app_mode, &self.packet),
|
|
(AppMode::Game, RenderSubmissionPacket::Game2D(_))
|
|
| (AppMode::Shell, RenderSubmissionPacket::ShellUi(_))
|
|
)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct RenderOwnership {
|
|
pub epoch: u64,
|
|
pub app_mode: AppMode,
|
|
pub app_id: u32,
|
|
}
|
|
|
|
impl RenderOwnership {
|
|
pub const fn new(epoch: u64, app_mode: AppMode, app_id: u32) -> Self {
|
|
Self { epoch, app_mode, app_id }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum RenderSubmissionPacket {
|
|
Game2D(Game2DFramePacket),
|
|
ShellUi(ShellUiFramePacket),
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
|
pub struct Game2DFramePacket {
|
|
pub composer: ComposerFramePacket,
|
|
pub gfx2d: Vec<Gfx2dCommand>,
|
|
}
|
|
|
|
impl Game2DFramePacket {
|
|
pub fn new(composer: ComposerFramePacket, gfx2d: Vec<Gfx2dCommand>) -> Self {
|
|
Self { composer, gfx2d }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct ShellUiFramePacket {
|
|
pub commands: Vec<GfxUiCommand>,
|
|
}
|
|
|
|
impl ShellUiFramePacket {
|
|
pub fn new(commands: Vec<GfxUiCommand>) -> Self {
|
|
Self { commands }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
|
pub struct ComposerFramePacket {
|
|
pub bound_scene: Option<BoundScenePacket>,
|
|
pub camera: Camera2D,
|
|
pub sprites: Vec<GameSpritePacket>,
|
|
pub hud: HudPacket,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
pub struct BoundScenePacket {
|
|
pub bank_id: i32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
|
pub struct Camera2D {
|
|
pub x: i32,
|
|
pub y: i32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub struct GameSpritePacket {
|
|
pub glyph_id: i32,
|
|
pub palette_id: i32,
|
|
pub x: i32,
|
|
pub y: i32,
|
|
pub layer: i32,
|
|
pub bank_id: i32,
|
|
pub flip_x: bool,
|
|
pub flip_y: bool,
|
|
pub priority: i32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
|
pub struct HudPacket {
|
|
pub commands: Vec<HudCommand>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum HudCommand {
|
|
Clear { color: Color },
|
|
FillRect { rect: Rect, color: Color },
|
|
DrawText { x: i32, y: i32, text: String, color: Color },
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum Gfx2dCommand {
|
|
Clear { color: Color },
|
|
FillRect { rect: Rect, color: Color },
|
|
DrawLine { x0: i32, y0: i32, x1: i32, y1: i32, color: Color },
|
|
DrawCircle { x: i32, y: i32, radius: i32, color: Color },
|
|
DrawDisc { x: i32, y: i32, radius: i32, border_color: Color, fill_color: Color },
|
|
DrawSquare { rect: Rect, border_color: Color, fill_color: Color },
|
|
DrawText { x: i32, y: i32, text: String, color: Color },
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
|
pub enum GfxUiCommand {
|
|
Clear { color: Color },
|
|
FillRect { rect: Rect, color: Color },
|
|
DrawLine { x0: i32, y0: i32, x1: i32, y1: i32, color: Color },
|
|
DrawCircle { x: i32, y: i32, radius: i32, color: Color },
|
|
DrawDisc { x: i32, y: i32, radius: i32, border_color: Color, fill_color: Color },
|
|
DrawSquare { rect: Rect, border_color: Color, fill_color: Color },
|
|
DrawText { x: i32, y: i32, text: String, color: Color },
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn constructors_pin_app_mode_to_packet_kind() {
|
|
let game = RenderSubmission::game2d(FrameId::new(7), Game2DFramePacket::default());
|
|
assert_eq!(game.app_mode, AppMode::Game);
|
|
assert!(matches!(game.packet, RenderSubmissionPacket::Game2D(_)));
|
|
assert!(game.is_coherent());
|
|
|
|
let shell =
|
|
RenderSubmission::shell_ui(FrameId::new(8), ShellUiFramePacket::new(Vec::new()));
|
|
assert_eq!(shell.app_mode, AppMode::Shell);
|
|
assert!(matches!(shell.packet, RenderSubmissionPacket::ShellUi(_)));
|
|
assert!(shell.is_coherent());
|
|
}
|
|
|
|
#[test]
|
|
fn coherence_check_rejects_mismatched_envelope() {
|
|
let submission = RenderSubmission {
|
|
frame_id: FrameId::new(1),
|
|
app_mode: AppMode::Game,
|
|
ownership: RenderOwnership::new(0, AppMode::Game, 0),
|
|
packet: RenderSubmissionPacket::ShellUi(ShellUiFramePacket::new(Vec::new())),
|
|
};
|
|
|
|
assert!(!submission.is_coherent());
|
|
}
|
|
|
|
#[test]
|
|
fn ownership_can_be_stamped_by_runtime() {
|
|
let submission = RenderSubmission::game2d(FrameId::new(3), Game2DFramePacket::default())
|
|
.with_ownership(RenderOwnership::new(9, AppMode::Game, 42));
|
|
|
|
assert_eq!(submission.ownership, RenderOwnership::new(9, AppMode::Game, 42));
|
|
}
|
|
|
|
#[test]
|
|
fn closed_submission_owns_command_payload() {
|
|
let mut commands = vec![Gfx2dCommand::DrawText {
|
|
x: 1,
|
|
y: 2,
|
|
text: "before".to_string(),
|
|
color: Color::WHITE,
|
|
}];
|
|
let submission = RenderSubmission::game2d(
|
|
FrameId::new(2),
|
|
Game2DFramePacket::new(Default::default(), commands.clone()),
|
|
);
|
|
|
|
commands.push(Gfx2dCommand::Clear { color: Color::BLACK });
|
|
|
|
let RenderSubmissionPacket::Game2D(packet) = submission.packet else {
|
|
panic!("expected game packet");
|
|
};
|
|
assert_eq!(packet.gfx2d.len(), 1);
|
|
assert_eq!(
|
|
packet.gfx2d[0],
|
|
Gfx2dCommand::DrawText { x: 1, y: 2, text: "before".to_string(), color: Color::WHITE }
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn frame_id_advances_monotonically_with_wrapping_arithmetic() {
|
|
assert_eq!(FrameId::new(41).next(), FrameId::new(42));
|
|
assert_eq!(FrameId::new(u64::MAX).next(), FrameId::ZERO);
|
|
}
|
|
}
|