meru-interface 0.3.0

Core interface for MERU multi emulator
Documentation
#[cfg(target_arch = "wasm32")]
#[macro_use]
extern crate base64_serde;

pub mod config;
pub mod key_assign;

pub use config::File;

use schemars::{
    gen::SchemaGenerator,
    schema::{Schema, SchemaObject},
    JsonSchema,
};
use serde::{de::DeserializeOwned, Deserialize, Serialize};

pub use crate::key_assign::{
    Gamepad, GamepadAxis, GamepadAxisType, GamepadButton, GamepadButtonType, InputState, KeyAssign,
    KeyCode, MultiKey, SingleKey,
};

pub struct CoreInfo {
    pub system_name: &'static str,
    pub abbrev: &'static str,
    pub file_extensions: &'static [&'static str],
}

#[derive(Default)]
pub struct FrameBuffer {
    pub width: usize,
    pub height: usize,
    pub buffer: Vec<Color>,
}

impl FrameBuffer {
    pub fn new(width: usize, height: usize) -> Self {
        let mut ret = Self::default();
        ret.resize(width, height);
        ret
    }

    pub fn resize(&mut self, width: usize, height: usize) {
        if (width, height) == (self.width, self.height) {
            return;
        }
        self.width = width;
        self.height = height;
        self.buffer.resize(width * height, Color::default());
    }

    pub fn pixel(&self, x: usize, y: usize) -> &Color {
        &self.buffer[y * self.width + x]
    }

    pub fn pixel_mut(&mut self, x: usize, y: usize) -> &mut Color {
        &mut self.buffer[y * self.width + x]
    }
}

#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub struct Color {
    pub r: u8,
    pub g: u8,
    pub b: u8,
}

impl JsonSchema for Color {
    fn schema_name() -> String {
        "Color".to_string()
    }

    fn json_schema(gen: &mut SchemaGenerator) -> Schema {
        let mut schema: SchemaObject = <String>::json_schema(gen).into();
        schema.format = Some("color".to_owned());
        schema.into()
    }
}

#[derive(thiserror::Error, Debug)]
pub enum ParseColorError {
    #[error("Color string must be hex color code: `#RRGGBB`")]
    InvalidFormat,
}

impl TryFrom<String> for Color {
    type Error = ParseColorError;

    fn try_from(s: String) -> Result<Self, Self::Error> {
        if s.len() != 7 || &s[0..1] != "#" || !s[1..].chars().all(|c| c.is_ascii_hexdigit()) {
            Err(ParseColorError::InvalidFormat)?;
        }

        Ok(Color {
            r: u8::from_str_radix(&s[1..3], 16).unwrap(),
            g: u8::from_str_radix(&s[3..5], 16).unwrap(),
            b: u8::from_str_radix(&s[5..7], 16).unwrap(),
        })
    }
}

impl From<Color> for String {
    fn from(c: Color) -> Self {
        format!("#{:02X}{:02X}{:02X}", c.r, c.g, c.b)
    }
}

impl Color {
    pub const fn new(r: u8, g: u8, b: u8) -> Self {
        Self { r, g, b }
    }
}

pub struct AudioBuffer {
    pub sample_rate: u32,
    pub channels: u16,
    pub samples: Vec<AudioSample>,
}

impl Default for AudioBuffer {
    fn default() -> Self {
        Self {
            sample_rate: 48000,
            channels: 2,
            samples: vec![],
        }
    }
}

impl AudioBuffer {
    pub fn new(sample_rate: u32, channels: u16) -> Self {
        Self {
            sample_rate,
            channels,
            samples: vec![],
        }
    }
}

#[derive(Default, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AudioSample {
    pub left: i16,
    pub right: i16,
}

impl AudioSample {
    pub fn new(left: i16, right: i16) -> Self {
        Self { left, right }
    }
}

#[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize)]
pub struct KeyConfig {
    pub controllers: Vec<Vec<(String, KeyAssign)>>,
}

impl KeyConfig {
    pub fn input(&self, input_state: &impl InputState) -> InputData {
        let controllers = self
            .controllers
            .iter()
            .map(|keys| {
                keys.iter()
                    .map(|(key, assign)| (key.clone(), assign.pressed(input_state)))
                    .collect()
            })
            .collect();

        InputData { controllers }
    }
}

#[derive(Default)]
pub struct InputData {
    pub controllers: Vec<Vec<(String, bool)>>,
}

pub trait EmulatorCore {
    type Error: std::error::Error + Send + Sync + 'static;
    type Config: JsonSchema + Serialize + DeserializeOwned + Default;

    fn core_info() -> &'static CoreInfo;

    fn try_from_file(
        data: &[u8],
        backup: Option<&[u8]>,
        config: &Self::Config,
    ) -> Result<Self, Self::Error>
    where
        Self: Sized;
    fn game_info(&self) -> Vec<(String, String)>;

    fn set_config(&mut self, config: &Self::Config);

    fn exec_frame(&mut self, render_graphics: bool);
    fn reset(&mut self);

    fn frame_buffer(&self) -> &FrameBuffer;
    fn audio_buffer(&self) -> &AudioBuffer;

    fn default_key_config() -> KeyConfig;
    fn set_input(&mut self, input: &InputData);

    fn backup(&self) -> Option<Vec<u8>>;

    fn save_state(&self) -> Vec<u8>;
    fn load_state(&mut self, data: &[u8]) -> Result<(), Self::Error>;
}