pixel8-player 0.2.0

Pixel8 cart player over the console runtime: a windowed desktop backend or a static-musl KMS/evdev/ALSA backend for handhelds
//! A headless backend: presents nowhere, replays scripted input. Used by `--smoke` and tests.

use crate::platform::{InputSnapshot, Platform};
use anyhow::Result;
use pixel8_runtime::fb::Framebuffer;
#[cfg(test)]
use std::{cell::RefCell, rc::Rc};

pub struct NullPlatform {
    scripted: std::collections::VecDeque<InputSnapshot>,
    presented: u32,
    #[cfg(test)]
    captured: Option<Rc<RefCell<Vec<Vec<u8>>>>>,
}

impl NullPlatform {
    pub fn new() -> NullPlatform {
        NullPlatform {
            scripted: Default::default(),
            presented: 0,
            #[cfg(test)]
            captured: None,
        }
    }

    #[cfg(test)]
    pub fn scripted(frames: Vec<InputSnapshot>) -> NullPlatform {
        NullPlatform {
            scripted: frames.into(),
            presented: 0,
            captured: None,
        }
    }

    #[cfg(test)]
    pub fn frames_presented(&self) -> u32 {
        self.presented
    }

    /// Like [`scripted`](Self::scripted), and also records every presented frame's pixels,
    /// in order, into the returned buffer.
    #[cfg(test)]
    pub fn scripted_with_capture(
        frames: Vec<InputSnapshot>,
    ) -> (NullPlatform, Rc<RefCell<Vec<Vec<u8>>>>) {
        let captured = Rc::new(RefCell::new(Vec::new()));
        (
            NullPlatform {
                scripted: frames.into(),
                presented: 0,
                captured: Some(captured.clone()),
            },
            captured,
        )
    }
}

impl Default for NullPlatform {
    fn default() -> NullPlatform {
        NullPlatform::new()
    }
}

impl Platform for NullPlatform {
    fn present(&mut self, _fb: &Framebuffer) -> Result<()> {
        self.presented += 1;
        #[cfg(test)]
        if let Some(captured) = &self.captured {
            captured.borrow_mut().push(_fb.pixels().to_vec());
        }
        Ok(())
    }

    fn poll(&mut self) -> InputSnapshot {
        self.scripted.pop_front().unwrap_or_default()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn scripted_then_default() {
        let mut snap = InputSnapshot::default();
        snap.buttons[4] = true;
        let mut p = NullPlatform::scripted(vec![snap]);
        assert!(p.poll().buttons[4], "first poll replays the script");
        assert!(!p.poll().buttons[4], "exhausted script yields default");
        p.present(&Framebuffer::new()).unwrap();
        assert_eq!(p.frames_presented(), 1);
    }
}