qwac 0.29.0

Rust client crate for making qwac games
Documentation
//! This is a simple crate that makes making qwac games easier.

pub mod audio;
pub mod canvas;
pub mod color;
pub mod data;
pub mod error;
pub mod input;
pub mod texture;
pub mod time;

/// Re-exported library crates
pub mod crates {
    pub use euclid;
    pub use log;
}

pub use canvas::{Canvas, OffscreenCanvas, Screen};
pub use color::Rgba;
pub use qwac_sys::core::LogLevel;
pub use qwac_sys::Buffer;
pub use texture::{ImageTexture, Texture};

use euclid::{Angle, Length, Point2D};
use qwac_sys::core;
use std::{future::Future, ops::RangeInclusive, pin::Pin, time::Duration};

/// Just represents the unit for euclidean transforms.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Pixels;

#[derive(Debug, Clone, PartialEq)]
pub struct Ellipse {
    pub center: Point2D<f32, Pixels>,
    pub radii: (Length<f32, Pixels>, Length<f32, Pixels>),
    pub rotation: Angle<f32>,
    pub range: RangeInclusive<Angle<f32>>,
    pub counter_clockwise: bool,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Arc {
    pub center: Point2D<f32, Pixels>,
    pub radius: Length<f32, Pixels>,
    pub range: RangeInclusive<Angle<f32>>,
    pub counter_clockwise: bool,
}

impl Default for Ellipse {
    fn default() -> Self {
        Self {
            center: Default::default(),
            radii: Default::default(),
            rotation: Default::default(),
            range: Angle::zero()..=Angle::two_pi(),
            counter_clockwise: false,
        }
    }
}

pub enum BootResult<T> {
    Sync(T),
    Async(Pin<Box<dyn Future<Output = T>>>),
}

pub trait Game: Sized {
    fn id() -> &'static str {
        ""
    }

    /// Create a new Game of this type, with a random u64 value (to seed RNGs and such).
    fn boot(random: u64) -> BootResult<Self>;

    /// Do rendering.  delta tells you the time between this render and the last
    /// one.
    fn render(&self, delta: Duration);

    /// Do processing.  delta tells you the time between this update and the
    /// last one.
    fn update<'a>(&'a mut self, delta: Duration) -> Option<Pin<Box<dyn Future<Output = ()> + 'a>>>;

    /// Shut down the game.  This can do things like save if necessary.
    fn shutdown(self) {}
}

pub fn log<S>(string: S, level: LogLevel)
where
    S: AsRef<str>,
{
    let string = string.as_ref();
    let pointer = string.as_ptr();
    let len = string.len() as i32;
    unsafe {
        core::log(pointer, len, level as i32);
    }
}

pub fn set_update_interval(interval: Duration) {
    unsafe {
        core::set_update_interval(interval.as_secs_f32());
    }
}

/// A simple logger that calls the qwac logging function.
struct Logger;

impl log::Log for Logger {
    #[cfg(debug_assertions)]
    fn enabled(&self, _: &log::Metadata) -> bool {
        true
    }

    #[cfg(not(debug_assertions))]
    fn enabled(&self, metadata: &log::Metadata) -> bool {
        metadata.level() <= log::Level::Info
    }

    fn log(&self, record: &log::Record) {
        let metadata = record.metadata();
        if self.enabled(metadata) {
            let level = match metadata.level() {
                log::Level::Error => LogLevel::Error,
                log::Level::Warn => LogLevel::Warning,
                log::Level::Info => LogLevel::Info,
                log::Level::Debug => LogLevel::Debug,
                log::Level::Trace => LogLevel::Log,
            };
            let mut message = String::new();
            message.push_str(metadata.target());
            message.push_str(" [");
            if let Some(file) = record.file() {
                message.push_str(&format!("{file}"));
            }
            if let Some(line) = record.line() {
                message.push(':');
                message.push_str(&format!("{line}"));
            }
            let args = record.args();
            message.push_str(&format!("]: {args}"));
            log(message, level);
        }
    }

    fn flush(&self) {}
}

const LOGGER: Logger = Logger;

/// Enable the log crate.
/// This is done automatically if the `game` macro is used.
pub fn enable_logging() {
    unsafe {
        log::set_logger_racy(&LOGGER).expect("Could not set logger");
    }
    #[cfg(debug_assertions)]
    unsafe {
        log::set_max_level_racy(log::LevelFilter::Trace);
    }

    #[cfg(not(debug_assertions))]
    unsafe {
        log::set_max_level_racy(log::LevelFilter::Info);
    }
}

/// Set up a synchronous QWAC game.
#[macro_export]
macro_rules! game {
    ($game_type:ty) => {
        static mut GAME: Option<$game_type> = None;

        #[no_mangle]
        pub extern "C" fn qwac_id() -> *const u8 {
            <$game_type as $crate::Game>::id().as_ptr()
        }

        #[no_mangle]
        pub extern "C" fn qwac_id_length() -> i32 {
            <$game_type as $crate::Game>::id()
                .len()
                .try_into()
                .expect("Out of range")
        }

        #[no_mangle]
        pub extern "C" fn qwac_game_boot(
            random: u64,
        ) -> *mut std::pin::Pin<Box<dyn std::future::Future<Output = ()>>> {
            #[cfg(debug_assertions)]
            {
                std::panic::set_hook(Box::new(|info| {
                    $crate::log(format!("PANIC: {info}"), $crate::LogLevel::Error)
                }));
            }

            $crate::enable_logging();

            async fn resolve_future(
                future: std::pin::Pin<Box<dyn std::future::Future<Output = $game_type>>>,
            ) -> () {
                let game = future.await;
                unsafe {
                    GAME = Some(game);
                }
            }
            match <$game_type as $crate::Game>::boot(random) {
                $crate::BootResult::Sync(game) => unsafe {
                    GAME = Some(game);
                    std::ptr::null_mut()
                },
                $crate::BootResult::Async(future) => {
                    Box::into_raw(Box::new(Box::pin(resolve_future(future))))
                }
            }
        }

        #[no_mangle]
        pub extern "C" fn qwac_game_render(frame_time: f64) {
            let game = unsafe { GAME.as_mut().unwrap() };

            $crate::Game::render(game, ::std::time::Duration::from_secs_f64(frame_time));
        }

        #[no_mangle]
        pub extern "C" fn qwac_game_update(
            frame_time: f64,
        ) -> *mut std::pin::Pin<Box<dyn std::future::Future<Output = ()>>> {
            let game = unsafe { GAME.as_mut().unwrap() };

            match $crate::Game::update(game, ::std::time::Duration::from_secs_f64(frame_time)) {
                Some(future) => Box::into_raw(Box::new(future)),
                None => std::ptr::null_mut(),
            }
        }

        #[no_mangle]
        pub extern "C" fn qwac_game_shutdown() {
            let game = unsafe { GAME.take().unwrap() };

            $crate::Game::shutdown(game);
        }
    };
}