Skip to content

Commit

Permalink
Started asset manager.
Browse files Browse the repository at this point in the history
  • Loading branch information
facundo-villa committed Feb 15, 2024
1 parent a10b600 commit 8a62fac
Show file tree
Hide file tree
Showing 19 changed files with 1,277 additions and 1,024 deletions.
17 changes: 17 additions & 0 deletions resource_management/src/asset/asset_handler.rs
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>>>;
}
117 changes: 117 additions & 0 deletions resource_management/src/asset/asset_manager.rs
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));
}
}
135 changes: 135 additions & 0 deletions resource_management/src/asset/audio_asset_handler.rs
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);
}
}
114 changes: 114 additions & 0 deletions resource_management/src/asset/image_asset_handler.rs
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()
}
}
Loading

0 comments on commit 8a62fac

Please sign in to comment.