#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SceneManifestConfig {
pub validate_paths: bool,
pub pretty: bool,
}
#[derive(Debug, Clone)]
pub struct ManifestEntry {
pub id: String,
pub asset_type: String,
pub path: String,
pub size_bytes: u64,
}
#[derive(Debug, Clone)]
pub struct SceneManifest {
pub scene_name: String,
pub entries: Vec<ManifestEntry>,
pub total_bytes: usize,
}
pub fn default_scene_manifest_config() -> SceneManifestConfig {
SceneManifestConfig {
validate_paths: true,
pretty: true,
}
}
pub fn new_scene_manifest(scene_name: &str) -> SceneManifest {
SceneManifest {
scene_name: scene_name.to_string(),
entries: Vec::new(),
total_bytes: 0,
}
}
pub fn manifest_add_entry(manifest: &mut SceneManifest, entry: ManifestEntry) {
manifest.entries.push(entry);
}
pub fn manifest_entry_count(manifest: &SceneManifest) -> usize {
manifest.entries.len()
}
pub fn manifest_find_by_type<'a>(
manifest: &'a SceneManifest,
type_tag: &str,
) -> Vec<&'a ManifestEntry> {
manifest
.entries
.iter()
.filter(|e| e.asset_type == type_tag)
.collect()
}
pub fn manifest_validate(manifest: &SceneManifest, cfg: &SceneManifestConfig) -> Vec<String> {
let mut errors: Vec<String> = Vec::new();
if manifest.scene_name.is_empty() {
errors.push("scene_name is empty".to_string());
}
if cfg.validate_paths {
for entry in &manifest.entries {
if entry.path.is_empty() {
errors.push(format!("entry '{}' has empty path", entry.id));
}
if entry.id.is_empty() {
errors.push("entry with empty id".to_string());
}
}
}
errors
}
pub fn manifest_total_size(manifest: &SceneManifest) -> u64 {
manifest.entries.iter().map(|e| e.size_bytes).sum()
}
pub fn manifest_to_json(manifest: &SceneManifest, cfg: &SceneManifestConfig) -> String {
let sep = if cfg.pretty { "\n " } else { "" };
let indent = if cfg.pretty { " " } else { "" };
let mut out = format!("{{\"scene\":\"{}\",\"entries\":[", manifest.scene_name);
if cfg.pretty {
out.push('\n');
}
for (i, entry) in manifest.entries.iter().enumerate() {
let comma = if i + 1 < manifest.entries.len() { "," } else { "" };
out.push_str(indent);
out.push_str(&format!(
"{{\"id\":\"{}\",\"type\":\"{}\",\"path\":\"{}\",\"size\":{}}}{}{}",
entry.id, entry.asset_type, entry.path, entry.size_bytes, comma, sep
));
}
if cfg.pretty && !manifest.entries.is_empty() {
}
out.push_str("]}");
out
}
pub fn manifest_write_to_file(
manifest: &mut SceneManifest,
cfg: &SceneManifestConfig,
_path: &str,
) -> usize {
let json = manifest_to_json(manifest, cfg);
manifest.total_bytes = json.len();
manifest.total_bytes
}
pub fn manifest_clear(manifest: &mut SceneManifest) {
manifest.entries.clear();
manifest.total_bytes = 0;
}
fn make_entry(id: &str, asset_type: &str, path: &str, size: u64) -> ManifestEntry {
ManifestEntry {
id: id.to_string(),
asset_type: asset_type.to_string(),
path: path.to_string(),
size_bytes: size,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_values() {
let cfg = default_scene_manifest_config();
assert!(cfg.validate_paths);
assert!(cfg.pretty);
}
#[test]
fn new_manifest_is_empty() {
let m = new_scene_manifest("TestScene");
assert_eq!(manifest_entry_count(&m), 0);
assert_eq!(m.scene_name, "TestScene");
}
#[test]
fn add_entry_increments_count() {
let mut m = new_scene_manifest("Scene");
manifest_add_entry(&mut m, make_entry("mesh_01", "mesh", "assets/body.obj", 1024));
assert_eq!(manifest_entry_count(&m), 1);
}
#[test]
fn find_by_type_filters_correctly() {
let mut m = new_scene_manifest("Scene");
manifest_add_entry(&mut m, make_entry("mesh_01", "mesh", "body.obj", 512));
manifest_add_entry(&mut m, make_entry("tex_01", "texture", "skin.png", 4096));
manifest_add_entry(&mut m, make_entry("mesh_02", "mesh", "hair.obj", 256));
let meshes = manifest_find_by_type(&m, "mesh");
assert_eq!(meshes.len(), 2);
let textures = manifest_find_by_type(&m, "texture");
assert_eq!(textures.len(), 1);
}
#[test]
fn total_size_sums_entries() {
let mut m = new_scene_manifest("Scene");
manifest_add_entry(&mut m, make_entry("a", "mesh", "a.obj", 100));
manifest_add_entry(&mut m, make_entry("b", "mesh", "b.obj", 200));
assert_eq!(manifest_total_size(&m), 300);
}
#[test]
fn validate_catches_empty_path() {
let mut m = new_scene_manifest("Scene");
manifest_add_entry(&mut m, make_entry("mesh_01", "mesh", "", 0));
let cfg = default_scene_manifest_config();
let errs = manifest_validate(&m, &cfg);
assert!(!errs.is_empty());
}
#[test]
fn validate_ok_when_all_valid() {
let mut m = new_scene_manifest("Scene");
manifest_add_entry(&mut m, make_entry("mesh_01", "mesh", "body.obj", 512));
let cfg = default_scene_manifest_config();
let errs = manifest_validate(&m, &cfg);
assert!(errs.is_empty());
}
#[test]
fn json_contains_scene_and_type() {
let mut m = new_scene_manifest("TestScene");
manifest_add_entry(&mut m, make_entry("mesh_01", "mesh", "body.obj", 512));
let cfg = default_scene_manifest_config();
let json = manifest_to_json(&m, &cfg);
assert!(json.contains("\"scene\""));
assert!(json.contains("\"type\""));
assert!(json.contains("mesh"));
}
#[test]
fn write_to_file_sets_total_bytes() {
let mut m = new_scene_manifest("Scene");
manifest_add_entry(&mut m, make_entry("mesh_01", "mesh", "body.obj", 512));
let cfg = default_scene_manifest_config();
let n = manifest_write_to_file(&mut m, &cfg, "/tmp/manifest.json");
assert!(n > 0);
assert_eq!(m.total_bytes, n);
}
#[test]
fn clear_resets_state() {
let mut m = new_scene_manifest("Scene");
manifest_add_entry(&mut m, make_entry("mesh_01", "mesh", "body.obj", 512));
let cfg = default_scene_manifest_config();
manifest_write_to_file(&mut m, &cfg, "/tmp/manifest.json");
manifest_clear(&mut m);
assert_eq!(manifest_entry_count(&m), 0);
assert_eq!(m.total_bytes, 0);
}
}