summaryrefslogtreecommitdiff
path: root/crates/teleia_macros/src/lib.rs
diff options
context:
space:
mode:
authorLLLL Colonq <llll@colonq>2025-04-11 13:24:40 -0400
committerLLLL Colonq <llll@colonq>2025-04-11 13:24:40 -0400
commit6f1269aab5f4d7e218e7f59bd82b34f6dcaed4a1 (patch)
tree0c8f8bac7207764d4e10d0f83c138530d580e695 /crates/teleia_macros/src/lib.rs
parent32ff6ce2d75e898cd350172916751ec13226d5f8 (diff)
Add asset macro
Diffstat (limited to 'crates/teleia_macros/src/lib.rs')
-rw-r--r--crates/teleia_macros/src/lib.rs146
1 files changed, 146 insertions, 0 deletions
diff --git a/crates/teleia_macros/src/lib.rs b/crates/teleia_macros/src/lib.rs
new file mode 100644
index 0000000..6395acf
--- /dev/null
+++ b/crates/teleia_macros/src/lib.rs
@@ -0,0 +1,146 @@
+use proc_macro::TokenStream;
+use std::path::Path;
+use std::collections::HashSet;
+use walkdir::WalkDir;
+use heck::ToUpperCamelCase;
+
+const BASE: &'static str = "src/assets/";
+
+#[derive(Debug, Hash, PartialEq, Eq)]
+struct Designator {
+ parts: Vec<String>,
+}
+impl Designator {
+ fn new(fbase: &str, path: &Path, shorten: bool) -> Self {
+ let mut parts: Vec<_> = path.strip_prefix(fbase).unwrap().with_extension("").components()
+ .filter_map(|c| match c {
+ std::path::Component::Normal(o) => Some(o.to_str().unwrap().to_owned()),
+ _ => None,
+ })
+ .collect();
+ if shorten {
+ parts.pop();
+ }
+ Self {
+ parts
+ }
+ }
+ fn enum_entry(&self, _fnm: &str) -> String {
+ let vs: Vec<_> = self.parts.iter().map(|s| s.to_uppercase()).collect();
+ format!("{}", self.parts.join(" ").to_upper_camel_case())
+ }
+ fn load_expr(&self, fnm: &str) -> Option<String> {
+ let i = format!("assets/{}/{}", fnm, self.parts.join("/"));
+ match fnm {
+ "meshes" =>
+ Some(format!("teleia::mesh::Mesh::from_obj(ctx, include_bytes!(\"{}.obj\"))", i)),
+ "textures" =>
+ Some(format!("teleia::texture::Texture::new(ctx, include_bytes!(\"{}.png\"))", i)),
+ "materials" =>
+ Some(format!("teleia::texture::Material::new(ctx, include_bytes!(\"{}/color.jpg\"), include_bytes!(\"{}/normal.jpg\"))", i, i)),
+ "shaders" => if self.parts.contains(&"nolib".to_owned()) {
+ Some(format!("teleia::shader::Shader::new_nolib(ctx, include_str!(\"{}/vert.glsl\"), include_str!(\"{}/frag.glsl\"))", i, i))
+ } else {
+ Some(format!("teleia::shader::Shader::new(ctx, include_str!(\"{}/vert.glsl\"), include_str!(\"{}/frag.glsl\"))", i, i))
+ },
+ _ => None,
+ }
+ }
+}
+
+#[derive(Debug)]
+struct Field {
+ nm: String,
+ entries: HashSet<Designator>,
+}
+impl Field {
+ fn new(nm: &str) -> Self {
+ let mut entries = HashSet::new();
+ let fbase = format!("{}{}", BASE, nm);
+ for mf in WalkDir::new(&fbase) {
+ if let Ok(f) = mf {
+ if f.file_type().is_file() {
+ entries.insert(Designator::new(
+ &fbase, f.path(),
+ nm == "shaders" || nm == "materials",
+ ));
+ }
+ }
+ }
+ Self {
+ nm: nm.to_owned(),
+ entries,
+ }
+ }
+ fn generate(&self) -> Option<(String, String, String)> {
+ let mut ents = Vec::new();
+ for d in self.entries.iter() {
+ if let Some(exp) = d.load_expr(&self.nm) {
+ ents.push((d.enum_entry(&self.nm), exp));
+ }
+ }
+ if ents.len() > 0 {
+ let (enm, ty) = match self.nm.as_str() {
+ "meshes" => ("Mesh", "teleia::mesh::Mesh"),
+ "textures" => ("Texture", "teleia::texture::Texture"),
+ "materials" => ("Material", "teleia::texture::Material"),
+ "shaders" => ("Shader", "teleia::shader::Shader"),
+ _ => panic!("unknown asset type: {}", self.nm),
+ };
+ let enums: Vec<_> = ents.iter().map(|(e, _)| e.clone()).collect();
+ let edecl = format!("#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, enum_map::Enum)]
+pub enum {} {{ {} }}", enm, enums.join(", "));
+ let decl = format!("pub {}: enum_map::EnumMap<{}, {}>", self.nm, enm, ty);
+ let inits: Vec<_> = ents.into_iter().map(|(e, exp)| format!("{}::{} => {}", enm, e, exp)).collect();
+ let init = format!("{}: enum_map::enum_map!({})", self.nm, inits.join(", "));
+ Some((edecl, decl, init))
+ } else {
+ None
+ }
+ }
+}
+
+#[derive(Debug)]
+struct AssetData {
+ fields: Vec<Field>,
+}
+impl AssetData {
+ fn new() -> Self {
+ let mut fields = Vec::new();
+ let dirs = std::fs::read_dir(BASE).expect("failed to read assets directory");
+ for dir in dirs {
+ if let Ok(d) = dir {
+ fields.push(Field::new(&d.file_name().into_string().unwrap()));
+ }
+ }
+ Self {
+ fields,
+ }
+ }
+ fn generate(&self) -> String {
+ let mut res = String::new();
+ let fdata: Vec<_> = self.fields.iter().filter_map(|f| f.generate()).collect();
+ for (edecl, _, _) in fdata.iter() {
+ res += edecl; res += "\n";
+ }
+ res += "pub struct Assets {\n";
+ res += "pub font_default: teleia::font::Bitmap,\n";
+ for (_, decl, _) in fdata.iter() {
+ res += decl; res += ",\n";
+ }
+ res += "}\nimpl Assets {\npub fn new(ctx: &teleia::context::Context) -> Self {\nSelf {\n";
+ res += "font_default: teleia::font::Bitmap::new(ctx),\n";
+ for (_, _, init) in fdata.iter() {
+ res += init; res += ",\n";
+ }
+ res += "}\n}\n}\n";
+ res
+ }
+}
+
+#[proc_macro]
+pub fn generate_assets(_s: TokenStream) -> TokenStream {
+ let assets = AssetData::new();
+ println!("{}", assets.generate());
+ format!("{}", assets.generate()).parse().expect("failed to parse generate_assets result")
+}