Skip to content
Snippets Groups Projects
apack_handler.rs 8.58 KiB
Newer Older
Louis's avatar
Louis committed
/// 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;
Louis's avatar
Louis committed
use micro_asset_io::{APack, APackProcessingComplete};
use serde::{Deserialize, Serialize};

use crate::assets::asset_types::ldtk_project::LdtkProject;
Louis's avatar
Louis committed
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,
	},
}

Louis's avatar
Louis committed
#[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>,
Louis's avatar
Louis committed
}

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");
Louis's avatar
Louis committed

						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);
							},
						)
					}
Louis's avatar
Louis committed
				}
			}

			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
			}