Skip to content

Instantly share code, notes, and snippets.

@slyedoc
Last active July 27, 2024 17:52
Show Gist options
  • Select an option

  • Save slyedoc/4e8ff426db12beadabdef9010d702ef8 to your computer and use it in GitHub Desktop.

Select an option

Save slyedoc/4e8ff426db12beadabdef9010d702ef8 to your computer and use it in GitHub Desktop.
build script with load folders
/// Its a bad idea, but works
/// This build script generates the Assets Enums and GameAssets file which contains the
use std::env;
use std::{
fs::{self},
io::{self, Read, Write},
path::{Path, PathBuf},
};
// print macro
macro_rules! print_warning {
($($tokens: tt)*) => {
println!("cargo:warning={}", format!($($tokens)*))
}
}
// Note: Yea this is out f control
fn main() -> io::Result<()> {
let out_dir = env::var("OUT_DIR").unwrap();
let file_path = Path::new(&out_dir).join("build_enums.rs");
let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();
let assets_dir = PathBuf::from(&manifest_dir).join("assets");
let existing_contents = if file_path.exists() {
fs::File::open(&file_path)
.and_then(|mut file| {
let mut contents = String::new();
file.read_to_string(&mut contents)?;
Ok(contents)
})
.unwrap_or_default()
} else {
String::new()
};
// Tell cargo to rerun this build script
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rerun-if-changed=assets/",);
// Generate file
let mut f = Vec::<u8>::new();
// number of assets to much, this is out of control
// loop though the assets and generate the enums
let mut settings = vec![
AssetSettings::new(
"fonts",
"FontFile",
"ttf",
"fonts",
"Handle<Font>",
),
AssetSettings::new(
"levels",
"Level",
"glb",
"levels",
"Handle<Gltf>",
), //.with_ext("Scene0"),
AssetSettings::new(
"blueprints",
"Blueprint",
"glb",
"blueprints",
"Handle<Gltf>",
),
AssetSettings::new(
"materials",
"MaterialFile",
"glb",
"materials",
"Handle<Gltf>",
),
AssetSettings::new(
"game_icons",
"GameIcon",
"png",
"icons/game",
"Handle<Image>",
),
AssetSettings::new(
"skyboxes",
"SkyboxFile",
"ktx2",
"skyboxes",
"Handle<Image>",
),
AssetSettings::new(
"musics",
"Music",
"ogg",
"music",
"Handle<bevy_kira_audio::AudioSource>",
),
AssetSettings::new(
"sounds",
"SoundFile",
"ogg",
"sounds",
"Handle<bevy_kira_audio::AudioSource>",
),
AssetSettings::new(
"sound_interface",
"SoundInterface",
"ogg",
"sounds/interface",
"Handle<bevy_kira_audio::AudioSource>",
),
AssetSettings::new(
"textures",
"TextureFile",
"png",
"textures",
"Handle<Image>",
),
AssetSettings::new(
"models",
"ModelFile",
"glb",
"models",
"Handle<Gltf>",
),
];
for a in settings.iter_mut() {
a.write_enum(&mut f, &assets_dir)?;
a.write_impl(&mut f)?;
}
// Write GameAssets
write!(
f,
r#"
#[derive(AssetCollection, Resource, Debug, Reflect)]
pub struct GameAssets {{
"#
)?;
for a in settings {
write!(
f,
r#"
#[asset(paths({list}), collection(typed, mapped))]
pub {asset_name}: HashMap<String, {type_str}>,
"#,
list = a.list.unwrap(),
asset_name = a.prop_name,
type_str = a.type_str
)?;
}
// Write GameAssets
write!(
f,
r#"
}}"#
)?;
let new_contents = String::from_utf8(f).unwrap();
let changed = !new_contents.eq(&existing_contents);
print_warning!("build file changed: {}", changed);
print_warning!("generated file path: {:?}", &file_path);
// Only write to file if the content is different...
if changed {
let mut file = fs::File::create(&file_path)?;
file.write_all(new_contents.as_bytes())?;
}
Ok(())
}
struct AssetSettings {
prop_name: &'static str,
enum_name: &'static str,
extension: &'static str,
path: &'static str,
type_str: &'static str,
list: Option<String>,
post_ext: Option<&'static str>,
}
impl AssetSettings {
pub fn new(
asset_name: &'static str,
enum_name: &'static str,
extension: &'static str,
path: &'static str,
type_str: &'static str,
) -> Self {
Self {
prop_name: asset_name,
enum_name,
extension,
path,
type_str,
list: None,
post_ext: None,
}
}
#[allow(dead_code)]
pub fn with_ext(mut self, post_ext: &'static str) -> Self {
self.post_ext = Some(post_ext);
self
}
pub fn write_enum(
&mut self,
mut f: impl io::Write,
assets_dir: &PathBuf,
) -> Result<(), io::Error> {
write!(f, "#[derive(Debug, clap::ValueEnum, EnumIter, EnumString, EnumProperty, Default, Component, States, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Reflect)]\n")?;
write!(
f,
"#[reflect(Default, Serialize, Deserialize)]\n"
)?;
write!(f, "pub enum {} {{\n", self.enum_name)?;
write!(f, " #[default]\n")?;
let dir_path = assets_dir.clone().join(self.path);
let mut entries = fs::read_dir(dir_path)?
.filter_map(|e| e.ok())
.filter(|e| {
e.path()
.extension()
.map_or(false, |ext| ext == self.extension)
})
.collect::<Vec<_>>();
entries.sort_by(|a, b| a.path().file_stem().cmp(&b.path().file_stem()));
let mut files = Vec::<String>::new();
for entry in entries {
let file_stem = entry
.path()
.file_stem()
.unwrap()
.to_str()
.unwrap()
.to_owned();
let sanitized_name = sanitize_file_name(&file_stem);
// Create a path relative to the project root
let file_path = entry.path();
let relative_path = file_path.strip_prefix(assets_dir).unwrap();
// Save the mapping from enum variant to file path
let mut path_str = relative_path.display().to_string().replace("\\", "/");
if let Some(post_ext) = self.post_ext {
path_str = format!("{}#{}", path_str, post_ext);
}
write!(
f,
r#" #[strum(props(path = "{path_str}"))]
{sanitized_name},
"#
)?;
files.push(path_str);
}
write!(f, "}}\n")?; // Close enum definition
self.list = Some(
files
.iter()
.map(|x| format!("\"{}\"", x))
.collect::<Vec<_>>()
.join(", "),
);
Ok(())
}
pub fn write_impl(&mut self, mut f: impl io::Write) -> Result<(), io::Error> {
write!(
f,
r#"impl {enum_name} {{
pub fn asset(&self, assets: &GameAssets) -> {type_str} {{
let file = self.get_str("path").unwrap();
let file_trimmed = file.split('#').next().unwrap_or(file);
assets.{prop}
.get(file_trimmed)
.unwrap_or_else(|| panic!("{enum_name} {{:?}} not found", self))
.clone()
}}
}}
"#,
enum_name = self.enum_name,
type_str = self.type_str,
prop = self.prop_name
)?;
// impl Music {
// pub fn asset(&self, assets: &GameAssets) ->
// Handle<bevy_kira_audio::AudioSource> {
// let file = self.get_str("path").unwrap();
// let file_trimmed = file.split('#').next().unwrap_or(file);
// assets.musics
// .get(file_trimmed)
// .unwrap_or_else(|| panic!("Music {:?} not found", self))
// .clone()
// }
// }
Ok(())
}
}
fn sanitize_file_name(name: &String) -> String {
name.split(|c: char| !c.is_alphanumeric())
.filter(|part| !part.is_empty())
.map(|part| {
let mut chars = part.chars();
chars
.next()
.unwrap()
.to_uppercase()
.chain(chars.flat_map(|c| c.to_lowercase()))
.collect::<String>()
})
.collect()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment