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
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
use crate::{
    path::AssetPath, AssetIo, AssetIoError, AssetMeta, AssetServer, Assets, Handle, HandleId,
    RefChangeChannel,
};
use anyhow::Error;
use anyhow::Result;
use bevy_ecs::system::{Res, ResMut};
use bevy_reflect::{TypeUuid, TypeUuidDynamic};
use bevy_utils::{BoxedFuture, HashMap};
use crossbeam_channel::{Receiver, Sender};
use downcast_rs::{impl_downcast, Downcast};
use std::path::Path;

/// A loader for an asset source.
///
/// Types implementing this trait are used by the asset server to load assets into their respective
/// asset storages.
pub trait AssetLoader: Send + Sync + 'static {
    /// Processes the asset in an asynchronous closure.
    fn load<'a>(
        &'a self,
        bytes: &'a [u8],
        load_context: &'a mut LoadContext,
    ) -> BoxedFuture<'a, Result<(), Error>>;

    /// Returns a list of extensions supported by this asset loader, without the preceding dot.
    fn extensions(&self) -> &[&str];
}

/// An essential piece of data of an application.
///
/// Assets are the building blocks of games. They can be anything, from images and sounds to scenes
/// and scripts. In Bevy, an asset is any struct that has an unique type id, as shown below:
///
/// ```rust
/// use bevy_reflect::TypeUuid;
/// use serde::Deserialize;
///
/// #[derive(Debug, Deserialize, TypeUuid)]
/// #[uuid = "39cadc56-aa9c-4543-8640-a018b74b5052"]
/// pub struct CustomAsset {
///     pub value: i32,
/// }
/// ```
///
/// See the `assets/custom_asset.rs` example in the repository for more details.
///
/// In order to load assets into your game you must either add them manually to an asset storage
/// with [`Assets::add`] or load them from the filesystem with [`AssetServer::load`].
pub trait Asset: TypeUuid + AssetDynamic {}

/// An untyped version of the [`Asset`] trait.
pub trait AssetDynamic: Downcast + TypeUuidDynamic + Send + Sync + 'static {}
impl_downcast!(AssetDynamic);

impl<T> Asset for T where T: TypeUuid + AssetDynamic + TypeUuidDynamic {}

impl<T> AssetDynamic for T where T: Send + Sync + 'static + TypeUuidDynamic {}

/// A complete asset processed in an [`AssetLoader`].
pub struct LoadedAsset<T: Asset> {
    pub(crate) value: Option<T>,
    pub(crate) dependencies: Vec<AssetPath<'static>>,
}

impl<T: Asset> LoadedAsset<T> {
    /// Creates a new loaded asset.
    pub fn new(value: T) -> Self {
        Self {
            value: Some(value),
            dependencies: Vec::new(),
        }
    }

    /// Adds a dependency on another asset at the provided path.
    pub fn add_dependency(&mut self, asset_path: AssetPath) {
        self.dependencies.push(asset_path.to_owned());
    }

    /// Adds a dependency on another asset at the provided path.
    #[must_use]
    pub fn with_dependency(mut self, asset_path: AssetPath) -> Self {
        self.add_dependency(asset_path);
        self
    }

    /// Adds dependencies on other assets at the provided paths.
    #[must_use]
    pub fn with_dependencies(mut self, asset_paths: Vec<AssetPath<'static>>) -> Self {
        for asset_path in asset_paths {
            self.add_dependency(asset_path);
        }
        self
    }
}

pub(crate) struct BoxedLoadedAsset {
    pub(crate) value: Option<Box<dyn AssetDynamic>>,
    pub(crate) dependencies: Vec<AssetPath<'static>>,
}

impl<T: Asset> From<LoadedAsset<T>> for BoxedLoadedAsset {
    fn from(asset: LoadedAsset<T>) -> Self {
        BoxedLoadedAsset {
            value: asset
                .value
                .map(|value| Box::new(value) as Box<dyn AssetDynamic>),
            dependencies: asset.dependencies,
        }
    }
}

/// An asynchronous context where an [`Asset`] is processed.
///
/// The load context is created by the [`AssetServer`] to process an asset source after loading its
/// contents into memory. It is then passed to the appropriate [`AssetLoader`] based on the file
/// extension of the asset's path.
///
/// An asset source can define one or more assets from a single source path. The main asset is set
/// using [`LoadContext::set_default_asset`] and sub-assets are defined with
/// [`LoadContext::set_labeled_asset`].
pub struct LoadContext<'a> {
    pub(crate) ref_change_channel: &'a RefChangeChannel,
    pub(crate) asset_io: &'a dyn AssetIo,
    pub(crate) labeled_assets: HashMap<Option<String>, BoxedLoadedAsset>,
    pub(crate) path: &'a Path,
    pub(crate) version: usize,
}

impl<'a> LoadContext<'a> {
    pub(crate) fn new(
        path: &'a Path,
        ref_change_channel: &'a RefChangeChannel,
        asset_io: &'a dyn AssetIo,
        version: usize,
    ) -> Self {
        Self {
            ref_change_channel,
            asset_io,
            labeled_assets: Default::default(),
            version,
            path,
        }
    }

    /// Gets the source path for this load context.
    pub fn path(&self) -> &Path {
        self.path
    }

    /// Returns `true` if the load context contains an asset with the specified label.
    pub fn has_labeled_asset(&self, label: &str) -> bool {
        self.labeled_assets.contains_key(&Some(label.to_string()))
    }

    /// Sets the primary asset loaded from the asset source.
    pub fn set_default_asset<T: Asset>(&mut self, asset: LoadedAsset<T>) {
        self.labeled_assets.insert(None, asset.into());
    }

    /// Sets a secondary asset loaded from the asset source.
    pub fn set_labeled_asset<T: Asset>(&mut self, label: &str, asset: LoadedAsset<T>) -> Handle<T> {
        assert!(!label.is_empty());
        self.labeled_assets
            .insert(Some(label.to_string()), asset.into());
        self.get_handle(AssetPath::new_ref(self.path(), Some(label)))
    }

    /// Gets a handle to an asset of type `T` from its id.
    pub fn get_handle<I: Into<HandleId>, T: Asset>(&self, id: I) -> Handle<T> {
        Handle::strong(id.into(), self.ref_change_channel.sender.clone())
    }

    /// Reads the contents of the file at the specified path through the [`AssetIo`] associated
    /// with this context.
    pub async fn read_asset_bytes<P: AsRef<Path>>(&self, path: P) -> Result<Vec<u8>, AssetIoError> {
        self.asset_io
            .watch_path_for_changes(path.as_ref(), Some(self.path.to_owned()))?;
        self.asset_io.load_path(path.as_ref()).await
    }

    /// Generates metadata for the assets managed by this load context.
    pub fn get_asset_metas(&self) -> Vec<AssetMeta> {
        let mut asset_metas = Vec::new();
        for (label, asset) in &self.labeled_assets {
            asset_metas.push(AssetMeta {
                dependencies: asset.dependencies.clone(),
                label: label.clone(),
                type_uuid: asset.value.as_ref().unwrap().type_uuid(),
            });
        }
        asset_metas
    }

    /// Gets the asset I/O associated with this load context.
    pub fn asset_io(&self) -> &dyn AssetIo {
        self.asset_io
    }
}

/// The result of loading an asset of type `T`.
#[derive(Debug)]
pub struct AssetResult<T> {
    /// The asset itself.
    pub asset: Box<T>,
    /// The unique id of the asset.
    pub id: HandleId,
    /// Change version.
    pub version: usize,
}

/// An event channel used by asset server to update the asset storage of a `T` asset.
#[derive(Debug)]
pub struct AssetLifecycleChannel<T> {
    /// The sender endpoint of the channel.
    pub sender: Sender<AssetLifecycleEvent<T>>,
    /// The receiver endpoint of the channel.
    pub receiver: Receiver<AssetLifecycleEvent<T>>,
}

/// Events for the [`AssetLifecycleChannel`].
pub enum AssetLifecycleEvent<T> {
    /// An asset was created.
    Create(AssetResult<T>),
    /// An asset was freed.
    Free(HandleId),
}

/// A trait for sending lifecycle notifications from assets in the asset server.
pub trait AssetLifecycle: Downcast + Send + Sync + 'static {
    /// Notifies the asset server that a new asset was created.
    fn create_asset(&self, id: HandleId, asset: Box<dyn AssetDynamic>, version: usize);
    /// Notifies the asset server that an asset was freed.
    fn free_asset(&self, id: HandleId);
}
impl_downcast!(AssetLifecycle);

impl<T: AssetDynamic> AssetLifecycle for AssetLifecycleChannel<T> {
    fn create_asset(&self, id: HandleId, asset: Box<dyn AssetDynamic>, version: usize) {
        if let Ok(asset) = asset.downcast::<T>() {
            self.sender
                .send(AssetLifecycleEvent::Create(AssetResult {
                    asset,
                    id,
                    version,
                }))
                .unwrap();
        } else {
            panic!(
                "Failed to downcast asset to {}.",
                std::any::type_name::<T>()
            );
        }
    }

    fn free_asset(&self, id: HandleId) {
        self.sender.send(AssetLifecycleEvent::Free(id)).unwrap();
    }
}

impl<T> Default for AssetLifecycleChannel<T> {
    fn default() -> Self {
        let (sender, receiver) = crossbeam_channel::unbounded();
        AssetLifecycleChannel { sender, receiver }
    }
}

/// Updates the [`Assets`] collection according to the changes queued up by [`AssetServer`].
pub fn update_asset_storage_system<T: Asset + AssetDynamic>(
    asset_server: Res<AssetServer>,
    assets: ResMut<Assets<T>>,
) {
    asset_server.update_asset_storage(assets);
}