#[cfg(feature = "ab_glyph")]
mod fonts;
#[cfg(feature = "gltf")]
#[cfg_attr(docsrs, doc(cfg(feature = "gltf")))]
mod gltf;
#[cfg(test)]
mod tests;
pub use crate::dirs::DirLoadable;
use crate::{AssetCache, BoxedError, error::ErrorKind, source::Source, utils::SharedString};
use std::{borrow::Cow, sync::Arc};
#[cfg(feature = "gltf")]
pub use self::gltf::Gltf;
#[cfg(doc)]
use crate::Handle;
pub trait FileAsset: Storable {
const EXTENSION: &'static str = "";
const EXTENSIONS: &'static [&'static str] = &[Self::EXTENSION];
fn from_bytes(bytes: Cow<[u8]>) -> Result<Self, BoxedError>;
#[inline]
#[expect(unused_variables)]
fn default_value(id: &SharedString, error: BoxedError) -> Result<Self, BoxedError> {
Err(error)
}
const HOT_RELOADED: bool = true;
}
pub trait Asset: Storable {
fn load(cache: &AssetCache, id: &SharedString) -> Result<Self, BoxedError>;
const HOT_RELOADED: bool = true;
}
#[deprecated = "Use `Asset` instead"]
pub trait Compound: Asset {
fn load(cache: &AssetCache, id: &SharedString) -> Result<Self, BoxedError>;
const HOT_RELOADED: bool = true;
}
#[expect(deprecated)]
impl<T> Compound for T
where
T: FileAsset,
{
#[inline]
fn load(cache: &AssetCache, id: &SharedString) -> Result<Self, BoxedError> {
let source = cache.source();
let load_with_ext = |ext| -> Result<T, ErrorKind> {
let asset = source
.read(id, ext)?
.with_cow(|content| T::from_bytes(content))?;
Ok(asset)
};
let mut error = ErrorKind::NoDefaultValue;
for ext in T::EXTENSIONS {
match load_with_ext(ext) {
Err(err) => error = err.or(error),
Ok(asset) => return Ok(asset),
}
}
T::default_value(id, error.into())
}
const HOT_RELOADED: bool = Self::HOT_RELOADED;
}
#[expect(deprecated)]
impl<T: Compound> Asset for T {
fn load(cache: &AssetCache, id: &SharedString) -> Result<Self, BoxedError> {
<T as Compound>::load(cache, id)
}
const HOT_RELOADED: bool = <T as Compound>::HOT_RELOADED;
}
impl<T> Asset for Arc<T>
where
T: Asset,
{
fn load(cache: &AssetCache, id: &SharedString) -> Result<Self, BoxedError> {
let asset = T::load(cache, id)?;
Ok(Arc::new(asset))
}
const HOT_RELOADED: bool = T::HOT_RELOADED;
}
pub trait Storable: Sized + Send + Sync + 'static {}
impl<T> Storable for T where T: Send + Sync + 'static {}
#[inline]
fn cow_bytes_to_str(bytes: Cow<[u8]>) -> Result<Cow<str>, std::str::Utf8Error> {
Ok(match bytes {
Cow::Borrowed(b) => Cow::Borrowed(std::str::from_utf8(b)?),
Cow::Owned(b) => Cow::Owned(String::from_utf8(b).map_err(|e| e.utf8_error())?),
})
}
macro_rules! string_assets {
( $( $typ:ty, )* ) => {
$(
impl FileAsset for $typ {
const EXTENSION: &'static str = "txt";
fn from_bytes(bytes: Cow<[u8]>) -> Result<Self, BoxedError> {
Ok(cow_bytes_to_str(bytes)?.into())
}
}
)*
}
}
string_assets! {
String, Box<str>, SharedString, Arc<str>,
}
#[cfg(feature = "bincode")]
#[cfg_attr(docsrs, doc(cfg(feature = "bincode")))]
pub fn load_bincode_standard<'de, T: serde_core::Deserialize<'de>>(
bytes: &'de [u8],
) -> Result<T, BoxedError> {
let (res, _) = bincode::serde::borrow_decode_from_slice(bytes, bincode::config::standard())?;
Ok(res)
}
#[cfg(feature = "bincode")]
#[cfg_attr(docsrs, doc(cfg(feature = "bincode")))]
pub fn load_bincode_legacy<'de, T: serde_core::Deserialize<'de>>(
bytes: &'de [u8],
) -> Result<T, BoxedError> {
let (res, _) = bincode::serde::borrow_decode_from_slice(bytes, bincode::config::legacy())?;
Ok(res)
}
#[cfg(feature = "json")]
#[cfg_attr(docsrs, doc(cfg(feature = "json")))]
pub fn load_json<'de, T: serde_core::Deserialize<'de>>(bytes: &'de [u8]) -> Result<T, BoxedError> {
serde_json::from_slice(bytes).map_err(Box::from)
}
#[cfg(feature = "msgpack")]
#[cfg_attr(docsrs, doc(cfg(feature = "msgpack")))]
pub fn load_msgpack<'de, T: serde_core::Deserialize<'de>>(
bytes: &'de [u8],
) -> Result<T, BoxedError> {
rmp_serde::from_slice(bytes).map_err(Box::from)
}
#[cfg(feature = "ron")]
#[cfg_attr(docsrs, doc(cfg(feature = "ron")))]
pub fn load_ron<'de, T: serde_core::Deserialize<'de>>(bytes: &'de [u8]) -> Result<T, BoxedError> {
ron::de::from_bytes(bytes).map_err(Box::from)
}
#[cfg(feature = "toml")]
#[cfg_attr(docsrs, doc(cfg(feature = "toml")))]
pub fn load_toml<'de, T: serde_core::Deserialize<'de>>(bytes: &'de [u8]) -> Result<T, BoxedError> {
toml::from_slice(bytes).map_err(Box::from)
}
#[cfg(feature = "yaml")]
#[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
pub fn load_yaml<'de, T: serde_core::Deserialize<'de>>(bytes: &'de [u8]) -> Result<T, BoxedError> {
serde_yaml::from_slice(bytes).map_err(Box::from)
}
pub fn load_text<T>(bytes: &[u8]) -> Result<T, BoxedError>
where
T: std::str::FromStr,
T::Err: Into<BoxedError>,
{
let str = std::str::from_utf8(bytes)?;
str.trim().parse().map_err(Into::into)
}
macro_rules! serde_assets {
(
$(
#[doc = $doc:literal]
#[cfg(feature = $feature:literal)]
struct $name:ident => (
[$($ext:literal),*],
$load:expr,
);
)*
) => {
$(
#[doc = $doc]
///
/// This type can directly be used as a [`FileAsset`] to load values
/// from an [`AssetCache`]. This is useful to load assets external
/// types without a newtype wrapper (eg [`Vec`]).
#[cfg(feature = $feature)]
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
#[derive(Debug, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(transparent)]
pub struct $name<T>(pub T);
#[cfg(feature = $feature)]
impl<T> Clone for $name<T>
where
T: Clone
{
fn clone(&self) -> Self {
Self(self.0.clone())
}
fn clone_from(&mut self, other: &Self) {
self.0.clone_from(&other.0)
}
}
#[cfg(feature = $feature)]
impl<T> From<T> for $name<T> {
#[inline]
fn from(t: T) -> Self {
Self(t)
}
}
#[cfg(feature = $feature)]
impl<T> $name<T> {
#[inline]
pub fn into_inner(self) -> T {
self.0
}
}
#[cfg(feature = $feature)]
impl<T> serde_core::Serialize for $name<T>
where
T: serde_core::Serialize,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde_core::Serializer,
{
self.0.serialize(serializer)
}
}
#[cfg(feature = $feature)]
impl<'de, T> serde_core::Deserialize<'de> for $name<T>
where
T: serde_core::Deserialize<'de>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde_core::Deserializer<'de>,
{
T::deserialize(deserializer).map($name)
}
fn deserialize_in_place<D>(deserializer: D, place: &mut Self) -> Result<(), D::Error>
where
D: serde_core::Deserializer<'de>,
{
T::deserialize_in_place(deserializer, &mut place.0)
}
}
#[cfg(feature = $feature)]
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
impl<T> FileAsset for $name<T>
where
T: for<'de> serde_core::Deserialize<'de> + Send + Sync + 'static,
{
const EXTENSIONS: &'static [&'static str] = &[$( $ext ),*];
fn from_bytes(bytes: Cow<[u8]>) -> Result<Self, BoxedError> {
$load(&*bytes).map(Self)
}
}
#[cfg(feature = $feature)]
impl<T> AsRef<T> for $name<T> {
#[inline]
fn as_ref(&self) -> &T {
&self.0
}
}
#[cfg(feature = $feature)]
impl<T> Default for $name<T>
where
T: Default,
{
#[inline]
fn default() -> Self {
Self(T::default())
}
}
)*
}
}
serde_assets! {
#[cfg(feature = "json")]
struct Json => (
["json"],
load_json,
);
#[cfg(feature = "ron")]
struct Ron => (
["ron"],
load_ron,
);
#[cfg(feature = "toml")]
struct Toml => (
["toml"],
load_toml,
);
#[cfg(feature = "yaml")]
struct Yaml => (
["yaml", "yml"],
load_yaml,
);
}
macro_rules! image_assets {
(
$(
#[doc = $doc:literal]
#[cfg(feature = $feature:literal)]
struct $name:ident => (
$format:path,
[$($ext:literal),*],
);
)*
) => {
$(
#[doc = $doc]
#[cfg(feature = $feature)]
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
#[derive(Clone, Debug)]
#[repr(transparent)]
pub struct $name(pub image::DynamicImage);
#[cfg(feature = $feature)]
#[cfg_attr(docsrs, doc(cfg(feature = $feature)))]
impl FileAsset for $name {
const EXTENSIONS: &'static [&'static str] = &[$( $ext ),*];
fn from_bytes(data: Cow<[u8]>) -> Result<Self, BoxedError> {
let img = image::load_from_memory_with_format(&data, $format)?;
Ok(Self(img))
}
}
)*
}
}
image_assets! {
#[cfg(feature = "bmp")]
struct Bmp => (
image::ImageFormat::Bmp,
["bmp"],
);
#[cfg(feature = "jpeg")]
struct Jpeg => (
image::ImageFormat::Jpeg,
["jpg", "jpeg"],
);
#[cfg(feature = "png")]
struct Png => (
image::ImageFormat::Png,
["png"],
);
#[cfg(feature = "webp")]
struct Webp => (
image::ImageFormat::WebP,
["webp"],
);
}