#![warn(missing_docs)]
#![cfg_attr(doc, allow(unknown_lints))]
#![deny(rustdoc::all)]
use std::{
any::TypeId,
collections::hash_map::Entry,
marker::PhantomData,
path::{Path, PathBuf},
sync::Arc,
};
use bones_ecs::{
prelude::{AtomicRefCell, Deref, DerefMut},
ulid::{TypeUlid, UlidMap},
};
pub mod prelude {
pub use crate::*;
}
#[derive(Default)]
pub struct AssetProviders {
providers: UlidMap<Box<dyn UntypedAssetProvider>>,
type_ids: UlidMap<TypeId>,
}
pub type ResAssetProviders<'a> = bones_ecs::system::Res<'a, AssetProvidersResource>;
#[derive(Deref, DerefMut, Clone, TypeUlid)]
#[ulid = "01GNWY5HKV5JZQRKG20ANJXHCK"]
pub struct AssetProvidersResource(pub Arc<AtomicRefCell<AssetProviders>>);
impl Default for AssetProvidersResource {
fn default() -> Self {
Self(Arc::new(AtomicRefCell::new(AssetProviders::default())))
}
}
impl AssetProviders {
pub fn add<T, A>(&mut self, provider: A)
where
T: TypeUlid + 'static,
A: AssetProvider<T> + UntypedAssetProvider + 'static,
{
let type_id = TypeId::of::<T>();
let type_ulid = T::ULID;
match self.type_ids.entry(type_ulid) {
Entry::Occupied(entry) => {
if entry.get() != &type_id {
panic!("Multiple Rust types with the same Type ULID");
}
}
Entry::Vacant(entry) => {
entry.insert(type_id);
}
}
self.providers.insert(type_ulid, Box::new(provider));
}
pub fn get<T: TypeUlid>(&self) -> AssetProviderRef<T> {
self.try_get::<T>().unwrap()
}
pub fn try_get<T: TypeUlid>(&self) -> Option<AssetProviderRef<T>> {
self.providers.get(&T::ULID).map(|x| {
let untyped = x.as_ref();
AssetProviderRef {
untyped,
_phantom: PhantomData,
}
})
}
pub fn get_mut<T: TypeUlid>(&mut self) -> AssetProviderMut<T> {
self.try_get_mut::<T>().unwrap()
}
pub fn try_get_mut<T: TypeUlid>(&mut self) -> Option<AssetProviderMut<T>> {
self.providers.get_mut(&T::ULID).map(|x| {
let untyped = x.as_mut();
AssetProviderMut {
untyped,
_phantom: PhantomData,
}
})
}
pub fn remove<T: TypeUlid>(&mut self) -> Box<dyn UntypedAssetProvider> {
self.try_remove::<T>().unwrap()
}
pub fn try_remove<T: TypeUlid>(&mut self) -> Option<Box<dyn UntypedAssetProvider>> {
self.providers.remove(&T::ULID)
}
}
pub trait UntypedAssetProvider: Sync + Send {
fn get(&self, handle: UntypedHandle) -> *const u8;
fn get_mut(&mut self, handle: UntypedHandle) -> *mut u8;
}
pub trait AssetProvider<T: TypeUlid>: Sync + Send {
fn get(&self, handle: Handle<T>) -> Option<&T>;
fn get_mut(&mut self, handle: Handle<T>) -> Option<&mut T>;
}
impl<T: TypeUlid> UntypedAssetProvider for dyn AssetProvider<T> {
fn get(&self, handle: UntypedHandle) -> *const u8 {
let asset = <Self as AssetProvider<T>>::get(self, handle.typed());
asset
.map(|x| x as *const T as *const u8)
.unwrap_or(std::ptr::null())
}
fn get_mut(&mut self, handle: UntypedHandle) -> *mut u8 {
let asset = <Self as AssetProvider<T>>::get_mut(self, handle.typed());
asset
.map(|x| x as *mut T as *mut u8)
.unwrap_or(std::ptr::null_mut())
}
}
pub struct AssetProviderRef<'a, T: TypeUlid> {
untyped: &'a dyn UntypedAssetProvider,
_phantom: PhantomData<T>,
}
impl<'a, T: TypeUlid> AssetProviderRef<'a, T> {
pub fn get(&self, handle: Handle<T>) -> Option<&T> {
let ptr = self.untyped.get(handle.untyped()) as *const T;
if ptr.is_null() {
None
} else {
unsafe { Some(&*ptr) }
}
}
}
pub struct AssetProviderMut<'a, T: TypeUlid> {
untyped: &'a mut dyn UntypedAssetProvider,
_phantom: PhantomData<T>,
}
impl<'a, T: TypeUlid> AssetProviderMut<'a, T> {
pub fn get(&self, handle: Handle<T>) -> Option<&T> {
let ptr = self.untyped.get(handle.untyped()) as *const T;
if ptr.is_null() {
None
} else {
unsafe { Some(&*ptr) }
}
}
pub fn get_mut(&mut self, handle: Handle<T>) -> Option<&mut T> {
let ptr = self.untyped.get_mut(handle.untyped()) as *mut T;
if ptr.is_null() {
None
} else {
unsafe { Some(&mut *ptr) }
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AssetPath {
pub path: Arc<Path>,
pub label: Option<Arc<str>>,
}
#[cfg(feature = "bevy")]
impl AssetPath {
pub fn get_bevy_asset_path(&self) -> bevy_asset::AssetPath<'static> {
bevy_asset::AssetPath::new(
if let Ok(path) = self.path.strip_prefix("/") {
path.to_path_buf()
} else {
self.path.to_path_buf()
},
self.label.as_ref().map(|x| x.to_string()),
)
}
}
impl AssetPath {
pub fn new<P: Into<PathBuf>>(path: P, label: Option<String>) -> Self {
AssetPath {
path: Arc::from(path.into()),
label: label.map(Arc::from),
}
}
pub fn normalize_relative_to(&mut self, base_path: &Path) {
fn normalize_path(path: &std::path::Path) -> std::path::PathBuf {
let mut components = path.components().peekable();
let mut ret = if let Some(c @ std::path::Component::Prefix(..)) = components.peek() {
let buf = std::path::PathBuf::from(c.as_os_str());
components.next();
buf
} else {
std::path::PathBuf::new()
};
for component in components {
match component {
std::path::Component::Prefix(..) => unreachable!(),
std::path::Component::RootDir => {
ret.push(component.as_os_str());
}
std::path::Component::CurDir => {}
std::path::Component::ParentDir => {
ret.pop();
}
std::path::Component::Normal(c) => {
ret.push(c);
}
}
}
ret
}
let is_relative = !self.path.starts_with(Path::new("/"));
let path = if is_relative {
let base = base_path.parent().unwrap_or_else(|| Path::new(""));
base.join(&self.path)
} else {
self.path.to_path_buf()
};
self.path = Arc::from(normalize_path(&path));
}
}
impl Default for AssetPath {
fn default() -> Self {
Self {
path: Arc::from(PathBuf::default()),
label: Default::default(),
}
}
}
#[derive(PartialEq, Eq, Hash)]
pub struct Handle<T: TypeUlid> {
pub path: AssetPath,
phantom: PhantomData<T>,
}
impl<T: TypeUlid> Clone for Handle<T> {
fn clone(&self) -> Self {
Self {
path: self.path.clone(),
phantom: self.phantom,
}
}
}
impl<T: TypeUlid> std::fmt::Debug for Handle<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Handle").field("path", &self.path).finish()
}
}
impl<T: TypeUlid> Handle<T> {
pub fn new<P: Into<PathBuf>>(path: P, label: Option<String>) -> Self {
Handle {
path: AssetPath::new(path, label),
phantom: PhantomData,
}
}
}
impl<T: TypeUlid> Default for Handle<T> {
fn default() -> Self {
Self {
path: AssetPath::default(),
phantom: Default::default(),
}
}
}
impl<T: TypeUlid> Handle<T> {
pub fn untyped(self) -> UntypedHandle {
UntypedHandle { path: self.path }
}
}
#[derive(Default, Clone, Debug, Hash, PartialEq, Eq)]
pub struct UntypedHandle {
pub path: AssetPath,
}
impl UntypedHandle {
pub fn new<P: Into<PathBuf>>(path: P, label: Option<String>) -> Self {
UntypedHandle {
path: AssetPath::new(path, label),
}
}
pub fn typed<T: TypeUlid>(self) -> Handle<T> {
Handle {
path: self.path,
phantom: PhantomData,
}
}
}
impl<'de> serde::Deserialize<'de> for UntypedHandle {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer.deserialize_str(UntypedHandleVisitor)
}
}
impl<'de, T: TypeUlid> serde::Deserialize<'de> for Handle<T> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
deserializer
.deserialize_str(UntypedHandleVisitor)
.map(UntypedHandle::typed)
}
}
impl serde::Serialize for UntypedHandle {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!(
"{}{}",
self.path.path.to_str().expect("Non-unicode path"),
self.path
.label
.as_ref()
.map(|x| format!("#{}", x))
.unwrap_or_default()
))
}
}
impl<T: TypeUlid> serde::Serialize for Handle<T> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&format!(
"{}{}",
self.path.path.to_str().expect("Non-unicode path"),
self.path
.label
.as_ref()
.map(|x| format!("#{}", x))
.unwrap_or_default()
))
}
}
struct UntypedHandleVisitor;
impl<'de> serde::de::Visitor<'de> for UntypedHandleVisitor {
type Value = UntypedHandle;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
formatter,
"A string path to an asset with an optional label."
)
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
let (path, label) = match v.rsplit_once('#') {
Some((path, label)) => (path, Some(label)),
None => (v, None),
};
Ok(UntypedHandle {
path: AssetPath::new(path, label.map(String::from)),
})
}
}
#[cfg(feature = "bevy")]
mod bevy {
use bevy_asset::{prelude::*, Asset, AssetPath};
use bones_bevy_utils::*;
use bones_ecs::ulid::TypeUlid;
impl IntoBevy<AssetPath<'static>> for super::AssetPath {
fn into_bevy(self) -> AssetPath<'static> {
AssetPath::new(self.path.to_path_buf(), self.label.map(|x| x.to_string()))
}
}
impl<T: Asset + TypeUlid> super::Handle<T> {
pub fn get_bevy_handle(&self) -> Handle<T> {
let asset_path = self.path.get_bevy_asset_path();
Handle::weak(asset_path.into())
}
}
impl<T: TypeUlid> super::Handle<T> {
pub fn get_bevy_handle_untyped(&self) -> HandleUntyped {
let asset_path = self.path.get_bevy_asset_path();
HandleUntyped::weak(asset_path.into())
}
}
impl super::UntypedHandle {
pub fn get_bevy_handle(&self) -> HandleUntyped {
let asset_path = self.path.get_bevy_asset_path();
HandleUntyped::weak(asset_path.into())
}
}
}