1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
use crossbeam_channel::Sender;
use downcast_rs::{impl_downcast, Downcast};
use rustc_hash::FxHashMap;
use std::{ffi::OsStr, path::PathBuf};
use zengine_macro::Resource;
use crate::{
handle::{HandleId, HandleRef},
Handle,
};
/// An Asset rappresent any kind of external data like
/// images, sound, text file etc..
///
/// To load and asset into you game you have to load from
/// the filesystem with [AssetManager::load](crate::AssetManager::load)
///
/// You should avoid to implement the Asset trait manually and use instead
/// the [asset derive macro](zengine_macro::Asset)
pub trait Asset: Downcast + Send + Sync + std::fmt::Debug + 'static {
/// Returns an unique asset id.
///
/// Normally it's used the asset path as unique id but in case
/// this is not possible (eg: get multiple id for the same asset path)
/// the engine will call this function.
///
/// The [asset derive macro](zengine_macro::Asset) implement this function
/// in the following way
///
/// ```ignore
/// static ASSETTEST_COUNTER: std::sync::atomic::AtomicU64 = std::sync::atomic::AtomicU64::new(0);
/// impl Asset for AssetTest {
/// fn next_counter() -> u64
/// where
/// Self: Sized,
/// {
/// ASSETTEST_COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
/// }
///}
/// ```
fn next_counter() -> u64
where
Self: Sized;
}
impl_downcast!(Asset);
/// A [Resource](zengine_ecs::Resource) that stores Assets of a given type
///
/// Each asset is mapped by an unique [`HandleId`], allowing any [`Handle`] with the same
/// [`HandleId`] to access it.
/// One asset remain loaded as long as a Strong handle to that asset exists.
///
/// To get a reference to an asset without forcing it to stay loadid you can use a Weak handle
#[derive(Resource, Debug)]
pub struct Assets<T: Asset> {
assets: FxHashMap<HandleId, T>,
pub(crate) sender: Sender<HandleRef>,
}
impl<T: Asset> Assets<T> {
pub(crate) fn new(sender: Sender<HandleRef>) -> Self {
Self {
assets: FxHashMap::default(),
sender,
}
}
/// Checks if an asset exists for the given handle
pub fn contains(&self, handle: &Handle<T>) -> bool {
self.assets.contains_key(&handle.id)
}
/// Get an asset reference for the given handle
pub fn get(&self, handle: &Handle<T>) -> Option<&T> {
self.assets.get(&handle.id)
}
/// Get a mutable asset reference for the given handle
pub fn get_mut(&mut self, handle: &Handle<T>) -> Option<&mut T> {
self.assets.get_mut(&handle.id)
}
/// Gets an iterator over the assets in the storage
pub fn iter(&self) -> impl Iterator<Item = (&HandleId, &T)> {
self.assets.iter()
}
/// Gets a mutable iterator over the assets in the storage
pub fn iter_mut(&mut self) -> impl Iterator<Item = (&HandleId, &mut T)> {
self.assets.iter_mut()
}
/// Add an asset to the storage returning a strong handle to that asset
pub fn add(&mut self, asset: T) -> Handle<T> {
let handle = Handle::strong(
HandleId::new_from_u64::<T>(T::next_counter()),
self.sender.clone(),
);
self.set_untracked(handle.id, asset);
handle
}
/// Add/Replace the asset pointed by the given handle
/// returning a strong handle to that asset
pub fn set(&mut self, handle: Handle<T>, asset: T) -> Handle<T> {
let id = handle.id;
self.set_untracked(id, asset);
Handle::strong(id, self.sender.clone())
}
/// Add/Replace the asset pointed by the given handle
pub fn set_untracked(&mut self, handle_id: HandleId, asset: T) {
self.assets.insert(handle_id, asset);
}
/// Remove the asset pointed by the given handle from the storage
///
/// The asset is returned
pub fn remove<H: Into<HandleId>>(&mut self, handle: H) -> Option<T> {
let id = handle.into();
self.assets.remove(&id)
}
/// Gets the number of assets in the storage
pub fn len(&self) -> usize {
self.assets.len()
}
/// Returns `true` if there are no stored assets
pub fn is_empty(&self) -> bool {
self.assets.is_empty()
}
}
/// Represents a path to an asset in the file system
#[derive(Debug)]
pub struct AssetPath {
pub(crate) path: PathBuf,
pub(crate) extension: String,
}
impl From<&str> for AssetPath {
fn from(file_path: &str) -> Self {
let path = std::path::Path::new(file_path);
let extension = path
.extension()
.and_then(OsStr::to_str)
.unwrap_or("")
.to_owned();
AssetPath {
path: path.into(),
extension,
}
}
}