/// This module is a cut down version of the currently WIP APack loader plugin that will /// accompany micro_asset_io. /// /// As such, it is missing many features and has been put together by cutting bits from various /// files. Have a peek for curiosity, but try not to learn from this particular file /// use std::ffi::OsStr; use std::path::PathBuf; use bevy::asset::Asset; use bevy::ecs::system::{SystemParam, SystemParamFetch, SystemState}; use bevy::math::vec2; use bevy::prelude::*; use bevy::render::texture::{CompressedImageFormats, ImageType}; use kayak_font::{KayakFont, Sdf}; use ldtk_rust::Project; use micro_asset_io::{APack, APackProcessingComplete}; use serde::{Deserialize, Serialize}; use crate::assets::asset_types::ldtk_project::LdtkProject; use crate::assets::AssetHandles; fn file_prefix(path: &PathBuf) -> Option<String> { path.file_name().and_then(|ss: &OsStr| { let lossy = ss.to_string_lossy(); let mut parts = lossy.split("."); parts.next().map(String::from) }) } trait BuildType { fn build_from_bytes(bytes: &Vec<u8>) -> Self; } impl BuildType for Image { fn build_from_bytes(bytes: &Vec<u8>) -> Self { Image::from_buffer( bytes.as_slice(), ImageType::Extension("png"), CompressedImageFormats::all(), true, ) .expect("Invalid PNG") } } #[derive(Deserialize, Serialize, Debug)] pub struct ManifestGenericAsset { pub name: String, pub path: String, } #[derive(Deserialize, Serialize, Debug)] #[serde(untagged)] pub enum ManifestFontAsset { Basic { name: String, path: String, }, MSDF { name: String, ttf: String, image: String, msdf: String, }, } #[derive(Deserialize, Serialize, Debug)] pub struct ManifestImage { pub name: String, pub path: String, pub format: String, } #[derive(Deserialize, Serialize, Debug)] pub struct ManifestSpriteSheetDescriptor { pub size: usize, pub rows: usize, pub columns: usize, } #[derive(Deserialize, Serialize, Debug)] pub struct ManifestSpriteSheet { pub name: String, pub path: String, pub tiles: ManifestSpriteSheetDescriptor, } #[derive(Deserialize, Serialize, Debug)] pub struct APackManifest { #[serde(default = "Vec::new")] pub images: Vec<ManifestImage>, #[serde(default = "Vec::new")] pub spritesheets: Vec<ManifestSpriteSheet>, #[serde(default = "Vec::new")] pub fonts: Vec<ManifestFontAsset>, #[serde(default = "Vec::new")] pub ldtk: Vec<ManifestGenericAsset>, } fn in_world<Param: SystemParam + 'static>( world: &mut World, cb: impl FnOnce(&mut <Param::Fetch as SystemParamFetch>::Item), ) { let mut state: SystemState<Param> = SystemState::new(world); let mut params = state.get_mut(world); cb(&mut params); } fn create_asset_type<Type: Asset + BuildType>( world: &mut World, bytes: &Vec<u8>, cb: impl FnOnce(Handle<Type>, &mut ResMut<AssetHandles>), ) { let mut state: SystemState<(ResMut<Assets<Type>>, ResMut<AssetHandles>)> = SystemState::new(world); let (mut assets, mut handles) = state.get_mut(world); let value = Type::build_from_bytes(bytes); let handle = assets.add(value); cb(handle, &mut handles); } pub fn handle_apack_process_events(world: &mut World) { let events: Vec<APackProcessingComplete> = { let mut state: SystemState<ResMut<Events<APackProcessingComplete>>> = SystemState::new(world); let mut events = state.get_mut(world); events.drain().collect() }; for event in events { let pack = { let mut state: SystemState<ResMut<Assets<APack>>> = SystemState::new(world); let mut packs = state.get_mut(world); if let Some(pack) = packs.get_mut(&event.0) { if pack.processed { std::mem::take(&mut pack.vfs) } else { continue; } } else { continue; } }; if let Some(raw) = pack.get("./manifest.toml") { let manifest: APackManifest = toml::from_slice(raw.as_slice()).expect("Badly formatted apack manifest"); for image in &manifest.images { let asset = pack .get(&format!("./{}", &image.path)) .expect("Missing asset"); if let Ok(image_data) = Image::from_buffer( asset.as_slice(), ImageType::Extension(&image.format), CompressedImageFormats::all(), true, ) { in_world::<ParamSet<(ResMut<Assets<Image>>, ResMut<AssetHandles>)>>( world, move |params: &mut ParamSet<( ResMut<Assets<Image>>, ResMut<AssetHandles>, )>| { let handle = params.p0().add(image_data); params.p1().images.insert(image.name.clone(), handle); }, ); } else { log::warn!("Malformed image {}", &image.path); } } for font in &manifest.fonts { match font { ManifestFontAsset::Basic { path, name } => { let asset = pack.get(&format!("./{}", &path)).expect("Missing asset"); if let Ok(font_data) = Font::try_from_bytes(asset.to_vec()) { in_world::<ParamSet<(ResMut<Assets<Font>>, ResMut<AssetHandles>)>>( world, move |params: &mut ParamSet<( ResMut<Assets<Font>>, ResMut<AssetHandles>, )>| { let handle = params.p0().add(font_data); params.p1().fonts.insert(name.clone(), handle); }, ); } else { log::warn!("Malformed font {}", path); } } ManifestFontAsset::MSDF { name, ttf, msdf, image, } => { let image_asset = pack.get(&format!("./{}", &image)).expect("Missing asset"); let ttf_asset = pack.get(&format!("./{}", &ttf)).expect("Missing asset"); let msdf_asset = pack.get(&format!("./{}", &msdf)).expect("Missing asset"); in_world::< ParamSet<( ResMut<Assets<Font>>, ResMut<Assets<Image>>, ResMut<Assets<KayakFont>>, ResMut<AssetHandles>, )>, >( world, move |params: &mut ParamSet<( ResMut<Assets<Font>>, ResMut<Assets<Image>>, ResMut<Assets<KayakFont>>, ResMut<AssetHandles>, )>| { let ttf_handle = params .p0() .add(Font::try_from_bytes(ttf_asset.to_vec()).unwrap()); let image_handle = params.p1().add( Image::from_buffer( image_asset.as_slice(), ImageType::Extension("png"), CompressedImageFormats::all(), true, ) .unwrap(), ); let msdf_handle = params.p2().add(KayakFont::new( Sdf::from_bytes(msdf_asset.as_slice()), kayak_font::ImageType::Atlas(image_handle.clone_weak()), )); let mut assets = params.p3(); assets.kayak_fonts.insert(name.clone(), msdf_handle); assets.images.insert(name.clone(), image_handle); assets.fonts.insert(name.clone(), ttf_handle); }, ) } } } for spritesheet in &manifest.spritesheets { let asset = pack .get(&format!("./{}", &spritesheet.path)) .expect("Missing asset"); if let Ok(image_data) = Image::from_buffer( asset.as_slice(), ImageType::Extension("png"), CompressedImageFormats::all(), true, ) { in_world::< ParamSet<( ResMut<Assets<Image>>, ResMut<Assets<TextureAtlas>>, ResMut<AssetHandles>, )>, >( world, move |params: &mut ParamSet<( ResMut<Assets<Image>>, ResMut<Assets<TextureAtlas>>, ResMut<AssetHandles>, )>| { let image_handle = params.p0().add(image_data); let texture_atlas = TextureAtlas::from_grid( image_handle.clone_weak(), Vec2::splat(spritesheet.tiles.size as f32), spritesheet.tiles.columns, spritesheet.tiles.rows, None, None, ); let atlas_handle = params.p1().add(texture_atlas); let mut assets = params.p2(); assets.images.insert(spritesheet.name.clone(), image_handle); assets.atlas.insert(spritesheet.name.clone(), atlas_handle); }, ); } else { log::warn!("Malformed spritesheet {}", &spritesheet.path); } } for ldtk_entry in &manifest.ldtk { let asset = pack .get(&format!("./{}", &ldtk_entry.path)) .expect("Missing asset"); let mut ldtk_file: Project = serde_json::from_slice(asset.as_slice()).unwrap(); in_world::<ParamSet<(ResMut<Assets<LdtkProject>>, ResMut<AssetHandles>)>>( world, move |params: &mut ParamSet<( ResMut<Assets<LdtkProject>>, ResMut<AssetHandles>, )>| { let handle = params.p0().add(LdtkProject(ldtk_file)); params.p1().ldtk.insert(ldtk_entry.name.clone(), handle); }, ); } } log::info!("LOADED AN APACK"); { let mut state: SystemState<ResMut<Assets<APack>>> = SystemState::new(world); let mut packs = state.get_mut(world); if let Some(mut pack) = packs.get_mut(&event.0) { pack.loaded = true } } } }