-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a10b600
commit 8a62fac
Showing
19 changed files
with
1,277 additions
and
1,024 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Option<Result<(), String>>>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
use super::asset_handler::AssetHandler; | ||
|
||
struct AssetManager { | ||
asset_handlers: Vec<Box<dyn AssetHandler>>, | ||
} | ||
|
||
/// 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<T: AssetHandler + 'static>(&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<Option<Result<(), String>>> { | ||
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)); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Option<Result<(), String>>> { | ||
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::<Audio>().unwrap(); | ||
|
||
// assert_eq!(audio.bit_depth, BitDepths::Sixteen); | ||
// assert_eq!(audio.channel_count, 1); | ||
// assert_eq!(audio.sample_rate, 48000); | ||
// assert_eq!(audio.sample_count, 152456 / 1 / (16 / 8)); | ||
|
||
// assert_eq!(buffer.len(), audio.sample_count as usize * audio.channel_count as usize * (Into::<usize>::into(audio.bit_depth) / 8) as usize); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
use utils::Extent; | ||
|
||
use crate::{types::{CompressionSchemes, Formats, Image}, GenericResourceSerialization}; | ||
|
||
use super::{asset_handler::AssetHandler, read_asset_from_source}; | ||
|
||
struct ImageAssetHandler { | ||
} | ||
|
||
impl ImageAssetHandler { | ||
fn new() -> ImageAssetHandler { | ||
ImageAssetHandler {} | ||
} | ||
} | ||
|
||
impl AssetHandler for ImageAssetHandler { | ||
fn load(&self, url: &str, json: &json::JsonValue) -> utils::BoxedFuture<Option<Result<(), String>>> { | ||
async move { | ||
let (data, dt) = read_asset_from_source(url, None).await.unwrap(); | ||
|
||
let mut decoder = png::Decoder::new(data.as_slice()); | ||
decoder.set_transformations(png::Transformations::normalize_to_color8()); | ||
let mut reader = decoder.read_info().unwrap(); | ||
let mut buffer = vec![0; reader.output_buffer_size()]; | ||
let info = reader.next_frame(&mut buffer).unwrap(); | ||
|
||
let extent = Extent::rectangle(info.width, info.height); | ||
|
||
assert_eq!(extent.depth(), 1); // TODO: support 3D textures | ||
|
||
let format = match info.color_type { | ||
png::ColorType::Rgb => { | ||
match info.bit_depth { | ||
png::BitDepth::Eight => Formats::RGB8, | ||
png::BitDepth::Sixteen => Formats::RGB16, | ||
_ => { panic!("Unsupported bit depth") } | ||
} | ||
} | ||
png::ColorType::Rgba => { | ||
match info.bit_depth { | ||
png::BitDepth::Eight => Formats::RGBA8, | ||
png::BitDepth::Sixteen => Formats::RGBA16, | ||
_ => { panic!("Unsupported bit depth") } | ||
} | ||
} | ||
_ => { panic!("Unsupported color type") } | ||
}; | ||
|
||
let (data, compression) = match format { | ||
Formats::RGB8 => { | ||
let mut buf: Vec<u8> = Vec::with_capacity(extent.width() as usize * extent.height() as usize * 4); | ||
|
||
for y in 0..extent.height() { | ||
for x in 0..extent.width() { | ||
let index = ((x + y * extent.width()) * 3) as usize; | ||
buf.push(data[index + 0]); | ||
buf.push(data[index + 1]); | ||
buf.push(data[index + 2]); | ||
buf.push(255); | ||
} | ||
} | ||
|
||
let rgba_surface = intel_tex_2::RgbaSurface { | ||
data: &buf, | ||
width: extent.width(), | ||
height: extent.height(), | ||
stride: extent.width() * 4, | ||
}; | ||
|
||
let settings = intel_tex_2::bc7::opaque_ultra_fast_settings(); | ||
|
||
(intel_tex_2::bc7::compress_blocks(&settings, &rgba_surface), Some(CompressionSchemes::BC7)) | ||
} | ||
Formats::RGB16 => { | ||
let mut buf: Vec<u8> = Vec::with_capacity(extent.width() as usize * extent.height() as usize * 8); | ||
|
||
for y in 0..extent.height() { | ||
for x in 0..extent.width() { | ||
let index = ((x + y * extent.width()) * 6) as usize; | ||
buf.push(data[index + 0]); buf.push(data[index + 1]); | ||
buf.push(data[index + 2]); buf.push(data[index + 3]); | ||
buf.push(data[index + 4]); buf.push(data[index + 5]); | ||
buf.push(255); buf.push(255); | ||
} | ||
} | ||
|
||
let rgba_surface = intel_tex_2::RgbaSurface { | ||
data: &buf, | ||
width: extent.width(), | ||
height: extent.height(), | ||
stride: extent.width() * 8, | ||
}; | ||
|
||
let settings = intel_tex_2::bc7::opaque_ultra_fast_settings(); | ||
|
||
(intel_tex_2::bc7::compress_blocks(&settings, &rgba_surface), Some(CompressionSchemes::BC7)) | ||
} | ||
_ => { | ||
(data, None) | ||
} | ||
}; | ||
|
||
let resource_document = GenericResourceSerialization::new(url.to_string(), Image { | ||
format, | ||
extent: extent.as_array(), | ||
compression, | ||
}); | ||
|
||
// Ok(vec![ProcessedResources::Generated((resource_document, data))]) | ||
|
||
Ok(()) | ||
}.boxed() | ||
} | ||
} |
Oops, something went wrong.