use std::path::{Path, PathBuf};
use std::fs::{self, File};
use std::io::{self, Write};
use std::ffi::OsStr;
use crate::atlas::form_atlas;
use crate::html;
use crate::rerun_print;
pub struct AssetPacker {
assets_dir: PathBuf,
check_rerun: bool,
mp3_fallback: bool,
sprites: Option<Vec<String>>,
music: Option<Vec<String>>,
sounds: Option<Vec<String>>,
js: bool,
}
impl AssetPacker {
pub fn new(assets_dir: &Path) -> AssetPacker {
fs::create_dir_all(assets_dir).expect("failed to create assets directory");
AssetPacker {
assets_dir: assets_dir.to_path_buf(),
sprites: None,
check_rerun: false,
mp3_fallback: false,
music: None,
sounds: None,
js: false,
}
}
pub fn cargo_rerun_if_changed(&mut self) {
assert!(self.sprites.is_none() && self.music.is_none() && self.sounds.is_none(),
"cannot add rerun checks after asset packing has already started");
self.check_rerun = true;
}
pub fn mp3_fallback(&mut self) {
assert!(self.music.is_none() && self.sounds.is_none(),
"cannot set mp3 fallback after audio asset packing has already started");
self.mp3_fallback = true;
}
pub fn sprites(&mut self, in_dir: &Path) -> &[String] {
assert!(self.sprites.is_none(), "self.sprites(...) was already invoked");
let output = &self.assets_dir.join("sprites");
self.sprites = Some(form_atlas(in_dir, output, 1, self.check_rerun));
self.sprites.as_ref().unwrap()
}
pub fn music(&mut self, in_dir: &Path) -> &[String] {
assert!(self.music.is_none(), "self.music(...) was already invoked");
self.music = Some(enumerate_audio(in_dir, &self.assets_dir, "music", self.mp3_fallback, self.check_rerun));
self.music.as_ref().unwrap()
}
pub fn sounds(&mut self, in_dir: &Path) -> &[String] {
assert!(self.sounds.is_none(), "self.sounds(...) was already invoked");
self.sounds = Some(enumerate_audio(in_dir, &self.assets_dir, "sound", self.mp3_fallback, self.check_rerun));
self.sounds.as_ref().unwrap()
}
pub fn gen_javascript_and_html(&mut self) {
self.gen_javascript();
create_file(&self.assets_dir, "index.html", html::INDEX_HTML, self.check_rerun);
}
pub fn gen_javascript(&mut self) {
assert!(!self.js, "javascript has already been generated");
self.js = true;
create_file(&self.assets_dir, "gate.js", html::GATE_JS, self.check_rerun);
}
pub fn gen_asset_id_code(self, out: &Path) {
self.gen_asset_id_code_checked(out).expect("error generating AssetId code")
}
fn gen_asset_id_code_checked(self, out: &Path) -> io::Result<()> {
let sprites_enum = gen_asset_enum("SpriteId", &self.sprites.expect("self.sprites(...) was not invoked"));
let music_enum = gen_asset_enum("MusicId", &self.music.unwrap_or(vec![]));
let sounds_enum = gen_asset_enum("SoundId", &self.sounds.unwrap_or(vec![]));
let code = format!(include_str!("asset_id.template.rs"), sprites_enum, music_enum, sounds_enum);
if let Some(out_dir) = out.parent() {
fs::create_dir_all(out_dir)?;
}
let mut file = File::create(out)?;
file.write_all(code.as_bytes())?;
Ok(())
}
}
fn create_file(out_dir: &Path, filename: &str, contents: &str, check_rerun: bool) {
let out_path = out_dir.join(filename);
File::create(&out_path).unwrap().write_all(contents.as_bytes()).unwrap();
rerun_print(check_rerun, &out_path);
}
fn enumerate_audio(in_dir: &Path, out_dir: &Path, prefix: &str, mp3_fallback: bool, check_rerun: bool) -> Vec<String> {
let mut paths: Vec<_> = in_dir.read_dir().unwrap()
.filter_map(|p| p.ok())
.map(|p| p.path())
.filter(|p| p.extension() == Some(OsStr::new("ogg")))
.collect();
paths.sort_unstable();
for (id, path) in paths.iter().enumerate() {
let out_path = out_dir.join(format!("{}{}.{}", prefix, id, "ogg"));
copy_file(path, &out_path, check_rerun);
if mp3_fallback {
copy_file(&path.with_extension("mp3"), &out_path.with_extension("mp3"), check_rerun);
}
}
paths.iter().map(|p| p.file_stem().unwrap().to_str().unwrap().to_owned()).collect()
}
fn copy_file(from: &Path, to: &Path, check_rerun: bool) {
rerun_print(check_rerun, from);
rerun_print(check_rerun, to);
fs::copy(from, to).unwrap_or_else(|err| match err.kind() {
io::ErrorKind::NotFound => panic!("Missing file: {:?}", from),
_ => panic!("{}", err),
});
}
fn gen_asset_enum(name: &str, ids: &[String]) -> String {
let mut ids_str = String::new();
for id in ids {
ids_str.push_str(" ");
ids_str.push_str(id);
ids_str.push_str(",\n");
}
if ids.len() == 0 {
format!(include_str!("asset_id_0.template.rs"), name)
} else if ids.len() <= 256 {
format!(include_str!("asset_id_u8.template.rs"), name, ids_str, ids.len())
} else if ids.len() <= 65536 {
format!(include_str!("asset_id_u16.template.rs"), name, ids_str, ids.len())
} else {
panic!("too many {} assets", name);
}
}