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