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.

Revisions

  1. slyedoc renamed this gist Jul 27, 2024. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion gistfile1.txt → build.rs
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    /// Based on work i did on bevy_sly_blender, its a bad idea, but works
    /// Its a bad idea, but works
    /// This build script generates the Assets Enums and GameAssets file which contains the
    use std::env;
  2. slyedoc created this gist Jul 27, 2024.
    331 changes: 331 additions & 0 deletions gistfile1.txt
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,331 @@
    /// Based on work i did on bevy_sly_blender, 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()
    }