diff --git a/resource_management/src/asset/asset_handler.rs b/resource_management/src/asset/asset_handler.rs new file mode 100644 index 00000000..28d4a774 --- /dev/null +++ b/resource_management/src/asset/asset_handler.rs @@ -0,0 +1,17 @@ +/// An asset handler is responsible for loading assets of a certain type from a url. +pub trait AssetHandler { + /// Load an asset from a url. + /// # Arguments + /// * `url` - The absolute url of the source asset. + /// * `json` - The JSON asset description. + /// ## Example + /// ```json + /// { + /// "url": "/path/to/asset", + /// } + /// ``` + /// # Returns + /// Returns Some(...) if the asset was managed by this handler, None otherwise. + /// Returns Some(Ok(...)) if the asset was loaded successfully, Some(Err(...)) otherwise. + fn load(&self, url: &str, json: &json::JsonValue) -> utils::BoxedFuture>>; +} \ No newline at end of file diff --git a/resource_management/src/asset/asset_manager.rs b/resource_management/src/asset/asset_manager.rs new file mode 100644 index 00000000..c1ba1208 --- /dev/null +++ b/resource_management/src/asset/asset_manager.rs @@ -0,0 +1,117 @@ +use super::asset_handler::AssetHandler; + +struct AssetManager { + asset_handlers: Vec>, +} + +/// Enumeration of the possible messages that can be returned when loading an asset. +#[derive(Debug, PartialEq, Eq)] +pub enum LoadMessages { + /// The URL was missing in the asset JSON. + NoURL, + /// No asset handler was found for the asset. + NoAssetHandler, +} + +impl AssetManager { + pub fn new() -> AssetManager { + AssetManager { + asset_handlers: Vec::new(), + } + } + + pub fn add_asset_handler(&mut self, asset_handler: T) { + self.asset_handlers.push(Box::new(asset_handler)); + } + + /// Load a source asset from a JSON asset description. + pub async fn load(&self, json: &json::JsonValue) -> Result<(), LoadMessages> { + let url = json["url"].as_str().ok_or(LoadMessages::NoURL)?; // Source asset url + + let asset_handler_loads = self.asset_handlers.iter().map(|asset_handler| asset_handler.load(url, &json)); + + let load_results = futures::future::join_all(asset_handler_loads).await; + + let asset_handler_found = load_results.iter().any(|load_result| { if let Some(Ok(_)) = load_result { true } else { false } }); + + if !asset_handler_found { + return Err(LoadMessages::NoAssetHandler); + } + + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use smol::future::FutureExt; + + use super::*; + + struct TestAssetHandler { + + } + + impl TestAssetHandler { + fn new() -> TestAssetHandler { + TestAssetHandler {} + } + } + + impl AssetHandler for TestAssetHandler { + fn load(&self, url: &str) -> utils::BoxedFuture>> { + let res = if url == "http://example.com" { + Some(Ok(())) + } else { + None + }; + + async move { res }.boxed() + } + } + + #[test] + fn test_new() { + let asset_manager = AssetManager::new(); + } + + #[test] + fn test_add_asset_manager() { + let mut asset_manager = AssetManager::new(); + + let test_asset_handler = TestAssetHandler::new(); + + asset_manager.add_asset_handler(test_asset_handler); + } + + #[test] + fn test_load_with_asset_manager() { + let mut asset_manager = AssetManager::new(); + + let test_asset_handler = TestAssetHandler::new(); + + asset_manager.add_asset_handler(test_asset_handler); + + let json = json::parse(r#"{"url": "http://example.com"}"#).unwrap(); + + assert_eq!(smol::block_on(asset_manager.load(&json)), Ok(())); + } + + #[test] + fn test_load_no_asset_handler() { + let asset_manager = AssetManager::new(); + + let json = json::parse(r#"{"url": "http://example.com"}"#).unwrap(); + + assert_eq!(smol::block_on(asset_manager.load(&json)), Err(LoadMessages::NoAssetHandler)); + } + + #[test] + fn test_load_no_asset_url() { + let asset_manager = AssetManager::new(); + + let json = json::parse(r#"{}"#).unwrap(); + + assert_eq!(smol::block_on(asset_manager.load(&json)), Err(LoadMessages::NoURL)); + } +} \ No newline at end of file diff --git a/resource_management/src/asset/audio_asset_handler.rs b/resource_management/src/asset/audio_asset_handler.rs new file mode 100644 index 00000000..c0a98ff9 --- /dev/null +++ b/resource_management/src/asset/audio_asset_handler.rs @@ -0,0 +1,135 @@ +use smol::future::FutureExt; + +use crate::types::{Audio, BitDepths}; + +use super::{asset_handler::AssetHandler, read_asset_from_source}; + +struct AudioAssetHandler { + +} + +impl AudioAssetHandler { + fn new() -> AudioAssetHandler { + AudioAssetHandler {} + } +} + +impl AssetHandler for AudioAssetHandler { + fn load(&self, url: &str, json: &json::JsonValue) -> utils::BoxedFuture>> { + async move { + let (data, dt) = read_asset_from_source(url, None).await.unwrap(); + + let riff = &data[0..4]; + + if riff != b"RIFF" { + return Err("Invalid RIFF header".to_string()); + } + + let format = &data[8..12]; + + if format != b"WAVE" { + return Err("Invalid WAVE format".to_string()); + } + + let audio_format = &data[20..22]; + + if audio_format != b"\x01\x00" { + return Err("Invalid audio format".to_string()); + } + + let subchunk_1_size = &data[16..20]; + + let subchunk_1_size = u32::from_le_bytes([subchunk_1_size[0], subchunk_1_size[1], subchunk_1_size[2], subchunk_1_size[3]]); + + if subchunk_1_size != 16 { + return Err("Invalid subchunk 1 size".to_string()); + } + + let num_channels = &data[22..24]; + + let num_channels = u16::from_le_bytes([num_channels[0], num_channels[1]]); + + if num_channels != 1 && num_channels != 2 { + return Err("Invalid number of channels".to_string()); + } + + let sample_rate = &data[24..28]; + + let sample_rate = u32::from_le_bytes([sample_rate[0], sample_rate[1], sample_rate[2], sample_rate[3]]); + + let bits_per_sample = &data[34..36]; + + let bits_per_sample = u16::from_le_bytes([bits_per_sample[0], bits_per_sample[1]]); + + let bit_depth = match bits_per_sample { + 8 => BitDepths::Eight, + 16 => BitDepths::Sixteen, + 24 => BitDepths::TwentyFour, + 32 => BitDepths::ThirtyTwo, + _ => { return Err("Invalid bits per sample".to_string()); } + }; + + let data_header = &data[36..40]; + + if data_header != b"data" { + return Err("Invalid data header".to_string()); + } + + let data_size = &data[40..44]; + + let data_size = u32::from_le_bytes([data_size[0], data_size[1], data_size[2], data_size[3]]); + + let sample_count = data_size / (bits_per_sample / 8) as u32 / num_channels as u32; + + let data = &data[44..][..data_size as usize]; + + let audio_resource = Audio { + bit_depth, + channel_count: num_channels, + sample_rate, + sample_count, + }; + + // Ok( + // vec![ + // ProcessedResources::Generated(( + // GenericResourceSerialization::new(asset_url.to_string(), audio_resource), + // Vec::from(data), + // )) + // ] + + // ) + + Ok(Some(())) + }.boxed() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_audio_asset_handler() { + let audio_asset_handler = AudioAssetHandler::new(); + + let url = "gun"; + let doc = json::object! { + "url": url, + }; + + smol::block_on(audio_asset_handler.load(url, &doc)).expect("Audio asset handler did not handle asset"); + + // assert_eq!(resource.url, "gun"); + // assert_eq!(resource.class, "Audio"); + + // let audio = resource.resource.downcast_ref::