qwac 0.29.0

Rust client crate for making qwac games
Documentation
use std::future::Future;
use std::pin::{pin, Pin};
use std::task::{Poll, Waker};

use euclid::Size2D;
use qwac_sys::graphics;

mod error;

use crate::{Canvas, Pixels, Rgba};

pub use self::error::CreateTextureError;

pub trait Texture {
    fn id(&self) -> i32;

    fn size(&self) -> Size2D<u16, Pixels> {
        let width = unsafe { graphics::texture_width(self.id()) }
            .try_into()
            .expect("out of range");
        let height = unsafe { graphics::texture_height(self.id()) }
            .try_into()
            .expect("out of range");
        Size2D::new(width, height)
    }
}

#[derive(Debug)]
pub struct ImageTexture {
    pub(crate) id: i32,
}

#[inline]
fn get_texture(texture: i32) -> Result<ImageTexture, CreateTextureError> {
    if texture >= 0 {
        Ok(ImageTexture { id: texture })
    } else {
        Err(CreateTextureError::from_code(!texture))
    }
}

extern "C" fn callback_trampoline<F>(texture: i32, userdata: *mut ())
where
    F: FnOnce(Result<ImageTexture, CreateTextureError>) + 'static,
{
    let func = *unsafe { Box::from_raw(userdata as *mut F) };
    func(get_texture(texture));
}

extern "C" fn async_callback(texture: i32, userdata: *mut ()) {
    let image_future = unsafe { Pin::new_unchecked(&mut *(userdata as *mut ImageFuture)) };
    image_future.set_result(get_texture(texture));
}

#[derive(Debug, Default)]
struct ImageFuture {
    result: Option<Result<ImageTexture, CreateTextureError>>,
    waker: Option<Waker>,
}

impl ImageFuture {
    fn set_result(mut self: Pin<&mut Self>, value: Result<ImageTexture, CreateTextureError>) {
        let result = self.as_mut().pin_get_result();
        *result = Some(value);
        if let Some(waker) = self.pin_get_waker().take() {
            waker.wake();
        }
    }
    fn pin_get_waker(self: Pin<&mut Self>) -> &mut Option<Waker> {
        unsafe { &mut self.get_unchecked_mut().waker }
    }
    fn pin_get_result(
        self: Pin<&mut Self>,
    ) -> &mut Option<Result<ImageTexture, CreateTextureError>> {
        unsafe { &mut self.get_unchecked_mut().result }
    }
    fn set_waker<'a, 'b>(self: Pin<&'a mut Self>, cx_waker: &'b Waker) {
        let waker = self.pin_get_waker();
        if let Some(waker) = waker.as_mut() {
            waker.clone_from(cx_waker);
        } else {
            *waker = Some(cx_waker.clone());
        }
    }
}

impl Future for ImageFuture {
    type Output = Result<ImageTexture, CreateTextureError>;

    fn poll(mut self: Pin<&mut Self>, cx: &mut core::task::Context<'_>) -> Poll<Self::Output> {
        match self.as_mut().pin_get_result().take() {
            Some(result) => Poll::Ready(result),
            None => {
                self.set_waker(cx.waker());
                Poll::Pending
            }
        }
    }
}

impl ImageTexture {
    pub async fn from_canvas<C>(canvas: &C) -> Result<Self, CreateTextureError>
    where
        C: Canvas,
    {
        let mut image_future = pin!(ImageFuture::default());
        unsafe {
            graphics::texture_from_canvas(
                canvas.id(),
                async_callback,
                image_future.as_mut().get_unchecked_mut() as *mut ImageFuture as *mut (),
            );
        }
        image_future.await
    }

    pub fn from_canvas_callback<C, F>(canvas: &C, callback: F)
    where
        C: Canvas,
        F: FnOnce(Result<Self, CreateTextureError>) + 'static,
    {
        unsafe {
            graphics::texture_from_canvas(
                canvas.id(),
                callback_trampoline::<F>,
                Box::into_raw(Box::new(callback)) as *mut (),
            );
        }
    }

    pub async fn from_rgba<P>(
        pixels: P,
        width: u32,
        height: u32,
    ) -> Result<Self, CreateTextureError>
    where
        P: AsRef<[u8]>,
    {
        let pixels = pixels.as_ref().as_ptr();
        let mut image_future = pin!(ImageFuture::default());
        unsafe {
            graphics::texture_from_rgba(
                pixels,
                width.try_into().expect("out of range"),
                height.try_into().expect("out of range"),
                async_callback,
                image_future.as_mut().get_unchecked_mut() as *mut ImageFuture as *mut (),
            );
        }
        image_future.await
    }

    pub fn from_rgba_callback<P, F>(pixels: P, width: u32, height: u32, callback: F)
    where
        P: AsRef<[u8]>,
        F: FnOnce(Result<Self, CreateTextureError>) + 'static,
    {
        let pixels = pixels.as_ref().as_ptr();
        unsafe {
            graphics::texture_from_rgba(
                pixels,
                width.try_into().expect("out of range"),
                height.try_into().expect("out of range"),
                callback_trampoline::<F>,
                Box::into_raw(Box::new(callback)) as *mut (),
            );
        }
    }

    pub async fn from_file<D, M>(data: D, mime_type: M) -> Result<Self, CreateTextureError>
    where
        D: AsRef<[u8]>,
        M: AsRef<str>,
    {
        let data = data.as_ref();
        let data_size: i32 = data.len().try_into().expect("out of range");
        let mime_type = mime_type.as_ref();
        let mime_type_size: i32 = mime_type.len().try_into().expect("out of range");
        let mut image_future = pin!(ImageFuture::default());
        unsafe {
            graphics::texture_from_file(
                data.as_ptr(),
                data_size,
                mime_type.as_ptr(),
                mime_type_size,
                async_callback,
                image_future.as_mut().get_unchecked_mut() as *mut ImageFuture as *mut (),
            );
        }
        image_future.await
    }

    pub fn from_file_callback<D, M, F>(data: D, mime_type: M, callback: F)
    where
        D: AsRef<[u8]>,
        M: AsRef<str>,
        F: FnOnce(Result<Self, CreateTextureError>) + 'static,
    {
        let data = data.as_ref();
        let data_size: i32 = data.len().try_into().expect("out of range");
        let mime_type = mime_type.as_ref();
        let mime_type_size: i32 = mime_type.len().try_into().expect("out of range");
        unsafe {
            graphics::texture_from_file(
                data.as_ptr(),
                data_size,
                mime_type.as_ptr(),
                mime_type_size,
                callback_trampoline::<F>,
                Box::into_raw(Box::new(callback)) as *mut (),
            );
        }
    }

    /// Build the texture from individual pixels.
    pub async fn from_pixels<P>(
        pixels: P,
        width: u32,
        height: u32,
    ) -> Result<Self, CreateTextureError>
    where
        P: IntoIterator,
        P::Item: Into<Rgba>,
    {
        let pixels: Vec<u8> = pixels
            .into_iter()
            .map(Into::into)
            .flat_map(|color| [color.r, color.g, color.b, color.a].into_iter())
            .collect();

        Self::from_rgba(pixels, width, height).await
    }

    /// Build the texture from individual pixels.
    pub fn from_pixels_callback<P, F>(pixels: P, width: u32, height: u32, callback: F)
    where
        P: IntoIterator,
        P::Item: Into<Rgba>,
        F: FnOnce(Result<Self, CreateTextureError>) + 'static,
    {
        let pixels: Vec<u8> = pixels
            .into_iter()
            .map(Into::into)
            .flat_map(|color| [color.r, color.g, color.b, color.a].into_iter())
            .collect();

        Self::from_rgba_callback(pixels, width, height, callback);
    }
}

impl Texture for ImageTexture {
    fn id(&self) -> i32 {
        self.id
    }
}

impl Drop for ImageTexture {
    fn drop(&mut self) {
        unsafe {
            graphics::texture_drop(self.id);
        }
    }
}