rend3-framework 0.3.0

Simple framework for making applications with the rend3 rendering library.
Documentation
use std::borrow::Cow;

use rend3::util::typedefs::SsoString;
use thiserror::Error;

#[derive(Debug, Error)]
pub enum AssetError {
    #[error("Could not read {path} from disk")]
    #[cfg(not(target_arch = "wasm32"))]
    FileError {
        path: SsoString,
        #[source]
        error: std::io::Error,
    },
    #[error("Could not read {path} from the network")]
    #[cfg(target_arch = "wasm32")]
    NetworkError {
        path: SsoString,
        #[source]
        error: reqwest::Error,
    },
    #[error("Reading {path} from the network returned non-success status code {status}")]
    #[cfg(target_arch = "wasm32")]
    NetworkStatusError {
        path: SsoString,
        status: reqwest::StatusCode,
    },
}

pub enum AssetPath<'a> {
    Internal(&'a str),
    External(&'a str),
}
impl<'a> AssetPath<'a> {
    fn get_path(self, base: &str) -> Cow<'a, str> {
        match self {
            Self::Internal(p) => Cow::Owned(base.to_owned() + p),
            Self::External(p) => Cow::Borrowed(p),
        }
    }
}

pub struct AssetLoader {
    base: SsoString,
}
impl AssetLoader {
    pub fn new_local(_base_file: &str, _base_asset: &str, _base_url: &str) -> Self {
        cfg_if::cfg_if!(
            if #[cfg(target_arch = "wasm32")] {
                let base = _base_url;
            } else if #[cfg(target_os = "android")] {
                let base = _base_asset;
            } else {
                let base = _base_file;
            }
        );

        Self {
            base: SsoString::from(base),
        }
    }

    pub fn get_asset_path<'a>(&self, path: AssetPath<'a>) -> Cow<'a, str> {
        path.get_path(&self.base)
    }

    #[cfg(all(not(target_arch = "wasm32"), not(target_os = "android")))]
    pub async fn get_asset(&self, path: AssetPath<'_>) -> Result<Vec<u8>, AssetError> {
        let full_path = path.get_path(&self.base);
        std::fs::read(&*full_path).map_err(|error| AssetError::FileError {
            path: SsoString::from(full_path),
            error,
        })
    }

    #[cfg(target_os = "android")]
    pub async fn get_asset(&self, path: AssetPath<'_>) -> Result<Vec<u8>, AssetError> {
        use std::ffi::CString;

        let manager = ndk_glue::native_activity().asset_manager();

        let full_path = path.get_path(&self.base);
        manager
            .open(&CString::new(&*full_path).unwrap())
            .ok_or_else(|| AssetError::FileError {
                path: SsoString::from(&*full_path),
                error: std::io::Error::new(std::io::ErrorKind::NotFound, "could not find file in asset manager"),
            })
            .and_then(|mut file| {
                file.get_buffer()
                    .map(|b| b.to_vec())
                    .map_err(|error| AssetError::FileError {
                        path: SsoString::from(full_path),
                        error,
                    })
            })
    }

    #[cfg(target_arch = "wasm32")]
    pub async fn get_asset(&self, path: AssetPath<'_>) -> Result<Vec<u8>, AssetError> {
        let full_path = path.get_path(&self.base);
        let response = reqwest::get(&*full_path)
            .await
            .map_err(|error| AssetError::NetworkError {
                path: SsoString::from(&*full_path),
                error,
            })?;

        let status = response.status();
        if !status.is_success() {
            return Err(AssetError::NetworkStatusError {
                path: SsoString::from(&*full_path),
                status,
            });
        }

        Ok(response
            .bytes()
            .await
            .map_err(|error| AssetError::NetworkError {
                path: SsoString::from(&*full_path),
                error,
            })?
            .to_vec())
    }
}