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
use std::sync::Arc;

use amethyst_core::specs::storage::UnprotectedStorage;

use crate::{ErrorKind, Handle, Reload, Result, ResultExt, SingleFile, Source};

/// One of the three core traits of this crate.
///
/// You want to implement this for every type of asset like
///
/// * `Mesh`
/// * `Texture`
/// * `Terrain`
///
/// and so on. Now, an asset may be available in different formats.
/// That's why we have the `Data` associated type here. You can specify
/// an intermediate format here, like the vertex data for a mesh or the samples
/// for audio data.
///
/// This data is then generated by the `Format` trait.
pub trait Asset: Send + Sync + 'static {
    /// An identifier for this asset used for debugging.
    const NAME: &'static str;

    /// The `Data` type the asset can be created from.
    type Data: Send + Sync + 'static;

    /// The ECS storage type to be used. You'll want to use `VecStorage` in most cases.
    type HandleStorage: UnprotectedStorage<Handle<Self>> + Send + Sync;
}

/// A format, providing a conversion from bytes to asset data, which is then
/// in turn accepted by `Asset::from_data`. Examples for formats are
/// `Png`, `Obj` and `Wave`.
pub trait Format<A: Asset>: Send + 'static {
    /// A unique identifier for this format.
    const NAME: &'static str;
    /// Options specific to the format, which are passed to `import`.
    /// E.g. for textures this would be stuff like mipmap levels and
    /// sampler info.
    type Options: Send + 'static;

    /// Reads the given bytes and produces asset data.
    ///
    /// ## Reload
    ///
    /// The reload structure has metadata which allows the asset management
    /// to reload assets if necessary (for hot reloading).
    /// You should only create this if `create_reload` is `true`.
    /// Also, the parameter is just a request, which means you can also return `None`.
    fn import(
        &self,
        name: String,
        source: Arc<dyn Source>,
        options: Self::Options,
        create_reload: bool,
    ) -> Result<FormatValue<A>>;
}

/// The `Ok` return value of `Format::import` for a given asset type `A`.
pub struct FormatValue<A: Asset> {
    /// The format data.
    pub data: A::Data,
    /// An optional reload structure
    pub reload: Option<Box<dyn Reload<A>>>,
}

impl<A: Asset> FormatValue<A> {
    /// Creates a `FormatValue` from only the data (setting `reload` to `None`).
    pub fn data(data: A::Data) -> Self {
        FormatValue { data, reload: None }
    }
}

/// This is a simplified version of `Format`, which doesn't give you as much freedom,
/// but in return is simpler to implement.
/// All `SimpleFormat` types automatically implement `Format`.
/// This format assumes that the asset name is the full path and the asset is only
/// contained in one file.
pub trait SimpleFormat<A: Asset> {
    /// A unique identifier for this format.
    const NAME: &'static str;
    /// Options specific to the format, which are passed to `import`.
    /// E.g. for textures this would be stuff like mipmap levels and
    /// sampler info.
    type Options: Clone + Send + Sync + 'static;

    /// Produces asset data from given bytes.
    fn import(&self, bytes: Vec<u8>, options: Self::Options) -> Result<A::Data>;
}

impl<A, T> Format<A> for T
where
    A: Asset,
    T: SimpleFormat<A> + Clone + Send + Sync + 'static,
{
    const NAME: &'static str = T::NAME;
    type Options = T::Options;

    fn import(
        &self,
        name: String,
        source: Arc<dyn Source>,
        options: Self::Options,
        create_reload: bool,
    ) -> Result<FormatValue<A>> {
        #[cfg(feature = "profiler")]
        profile_scope!("import_asset");
        if create_reload {
            let (b, m) = source
                .load_with_metadata(&name)
                .chain_err(|| ErrorKind::Source)?;
            let data = T::import(&self, b, options.clone())?;
            let reload = SingleFile::new(self.clone(), m, options, name, source);
            let reload = Some(Box::new(reload) as Box<dyn Reload<A>>);
            Ok(FormatValue { data, reload })
        } else {
            let b = source.load(&name).chain_err(|| ErrorKind::Source)?;
            let data = T::import(&self, b, options)?;

            Ok(FormatValue::data(data))
        }
    }
}