pub mod audio;
pub mod canvas;
pub mod color;
pub mod data;
pub mod error;
pub mod input;
pub mod texture;
pub mod time;
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};
#[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 {
""
}
fn boot(random: u64) -> BootResult<Self>;
fn render(&self, delta: Duration);
fn update<'a>(&'a mut self, delta: Duration) -> Option<Pin<Box<dyn Future<Output = ()> + 'a>>>;
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());
}
}
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;
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);
}
}
#[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);
}
};
}