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 {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<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<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<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<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))
        }
    }
}