use crate::ecs::{Ecs, EcsError};
use crate::snake_case_filter;
use minijinja::{Environment, context};
use std::fs::File;
use std::io::{BufReader, Write};
use std::{env, io};
#[derive(Default)]
pub struct EcsCode {
pub components: String,
pub archetypes: String,
pub systems: String,
pub world: String,
}
#[derive(thiserror::Error, Debug)]
pub enum WriteCodeError {
#[error("Could not access directory {0}: {1}")]
InvalidDirectory(String, io::Error),
#[error("Failed to open file {0}: {1}")]
FailedToOpenFile(String, io::Error),
#[error("Failed to write to file {0}: {1}")]
FailedToWriteFile(String, io::Error),
}
impl EcsCode {
pub fn generate<R>(reader: BufReader<R>) -> Result<EcsCode, EcsError>
where
R: io::Read,
{
let mut ecs: Ecs = serde_yaml::from_reader(reader).expect("Failed to deserialize ecs.yaml");
ecs.ensure_state_consistency()?;
ecs.ensure_component_consistency()?;
ecs.ensure_distinct_archetype_components()?;
ecs.ensure_system_consistency()?;
ecs.ensure_world_consistency()?;
ecs.finish()?;
let mut env = Environment::new();
env.add_filter("snake_case", snake_case_filter);
env.add_template("world", include_str!("../templates/world.rs.jinja2"))?;
env.add_template(
"components",
include_str!("../templates/components.rs.jinja2"),
)?;
env.add_template(
"archetypes",
include_str!("../templates/archetypes.rs.jinja2"),
)?;
env.add_template("systems", include_str!("../templates/systems.rs.jinja2"))?;
let world_code = env.get_template("world")?.render(context! {
ecs => ecs,
})?;
let component_code = env.get_template("components")?.render(context! {
ecs => ecs,
})?;
let archetype_code = env.get_template("archetypes")?.render(context! {
ecs => ecs,
})?;
let system_code = env.get_template("systems")?.render(context! {
ecs => ecs,
})?;
println!("{}", component_code);
println!("{}", archetype_code);
Ok(EcsCode {
components: component_code,
archetypes: archetype_code,
world: world_code,
systems: system_code,
..EcsCode::default()
})
}
pub fn write_files(&self) -> Result<(), WriteCodeError> {
let out_dir = env::var("OUT_DIR").map_err(|_| {
WriteCodeError::InvalidDirectory(
String::from("(OUT_DIR)"),
io::Error::new(
io::ErrorKind::NotADirectory,
"The specified path is not a directory",
),
)
})?;
self.write_files_to(out_dir)
}
pub fn write_files_to<P>(&self, out_dir: P) -> Result<(), WriteCodeError>
where
P: AsRef<str>,
{
let out_dir = out_dir.as_ref();
if !std::path::Path::new(out_dir).is_dir() {
return Err(WriteCodeError::InvalidDirectory(
out_dir.to_string(),
io::Error::new(
io::ErrorKind::NotADirectory,
"The specified path is not a directory",
),
));
}
Self::write_file(out_dir, "components_gen.rs", &self.components)?;
Self::write_file(out_dir, "archetypes_gen.rs", &self.archetypes)?;
Self::write_file(out_dir, "systems_gen.rs", &self.systems)?;
Self::write_file(out_dir, "world_gen.rs", &self.world)?;
Ok(())
}
fn write_file(out_dir: &str, file_name: &str, content: &str) -> Result<(), WriteCodeError> {
let path = format!("{out_dir}/{file_name}");
let mut file =
File::create(path).map_err(|e| WriteCodeError::FailedToOpenFile(e.to_string(), e))?;
file.write_all(content.as_bytes())
.map_err(|e| WriteCodeError::FailedToWriteFile(e.to_string(), e))?;
Ok(())
}
}