concinnity_cook/build_only/
mod.rs1pub mod preset;
7
8use crate::authoring::world::{WorldJsonlAsset, load_world};
9
10pub(crate) mod app_config;
11pub(crate) mod camera_shot;
12pub(crate) mod character_model;
13pub(crate) mod companion;
14pub(crate) mod companion_specs;
15
16pub(crate) mod light_rig;
17pub(crate) mod main_menu;
18pub(crate) mod material_palette;
19pub(crate) mod menu_defaults;
20pub(crate) mod option_select;
21pub(crate) mod panel;
22pub(crate) mod prefab;
23pub(crate) mod room;
24pub(crate) mod scene_import;
25pub(crate) mod slider;
26pub(crate) mod story;
27pub use story::validate_story_source;
28pub(crate) mod ui_spec;
29
30pub(crate) mod expand;
31mod provenance;
32pub use provenance::Provenance;
33pub(crate) mod shadow;
34pub use shadow::merge_args;
35
36pub(crate) use expand::expand_world;
37pub use expand::{GeneratedAsset, InjectedAsset, ShadowedAsset, expand_world_from_str};
38pub struct LoadedWorld {
41 pub assets: Vec<WorldJsonlAsset>,
43 pub injected: Vec<InjectedAsset>,
46 pub generated: Vec<GeneratedAsset>,
49 pub shadowed: Vec<ShadowedAsset>,
52 pub authored: Vec<String>,
55}
56
57pub fn prepare_world(
66 content: &str,
67 assets_dir: Option<&std::path::Path>,
68) -> Result<LoadedWorld, Vec<String>> {
69 let mut expanded = load_world(content)?;
70 let authored: Vec<String> = expanded
71 .iter()
72 .filter_map(|v| v.get("name").and_then(|n| n.as_str()))
73 .map(str::to_string)
74 .collect();
75 let report = expand_world(&mut expanded, assets_dir).map_err(|e| vec![e])?;
76 crate::cache::flush();
80
81 let assets: Vec<WorldJsonlAsset> = expanded.iter().map(WorldJsonlAsset::from_value).collect();
82
83 crate::check::check_world(&assets)?;
84
85 Ok(LoadedWorld {
86 assets,
87 injected: report.injected,
88 generated: report.generated,
89 shadowed: report.shadowed,
90 authored,
91 })
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
102 fn prepare_world_expands_and_validates() {
103 let content = r#"{"name":"gfx","type":"GraphicsConfig","args":{}}"#;
104 let loaded = prepare_world(content, None).unwrap();
105 assert!(loaded.assets.len() > 1);
108 assert!(
109 loaded
110 .assets
111 .iter()
112 .any(|a| a.asset_type == "GraphicsConfig")
113 );
114 assert_eq!(loaded.authored, vec!["gfx".to_string()]);
117 }
118
119 #[test]
122 fn prepare_world_reports_an_expansion_failure() {
123 let content = r#"{"name":"p","type":"Prop","args":{"prefab":"ghost"}}"#;
124 let errs = prepare_world(content, None).err().unwrap_or_default();
125 assert_eq!(errs.len(), 1);
126 assert!(errs[0].contains("ghost"), "{errs:?}");
127 }
128
129 #[test]
132 fn prepare_world_reports_semantic_errors() {
133 let content = r#"{"name":"prop","type":"Prop","args":{"mesh":"nope"}}"#;
134 let errs = prepare_world(content, None).err().unwrap_or_default();
135 assert!(!errs.is_empty());
136 assert!(errs.iter().any(|e| e.contains("nope")), "{errs:?}");
137 }
138
139 #[test]
144 fn prepare_world_expands_presets_from_the_root_it_is_given() {
145 fn rig_root(intensity: f64) -> tempfile::TempDir {
146 let dir = tempfile::tempdir().unwrap();
147 let rigs = dir.path().join("light_rigs");
148 std::fs::create_dir_all(&rigs).unwrap();
149 std::fs::write(
150 rigs.join("dusk.json"),
151 serde_json::to_vec(&serde_json::json!({
152 "args": {"lights": [{"kind": "directional", "name": "key", "intensity": intensity}]}
153 }))
154 .unwrap(),
155 )
156 .unwrap();
157 dir
158 }
159 fn key_intensity(loaded: &LoadedWorld) -> Option<f64> {
160 loaded
161 .assets
162 .iter()
163 .find(|a| a.name == "rig_key")?
164 .args
165 .get("intensity")?
166 .as_f64()
167 }
168
169 let content = r#"{"name":"rig","type":"LightRig","args":{"preset":"dusk"}}"#;
170 let bright = rig_root(3.5);
171 let dim = rig_root(0.25);
172
173 assert_eq!(
174 key_intensity(&prepare_world(content, Some(bright.path())).unwrap()),
175 Some(3.5)
176 );
177 assert_eq!(
178 key_intensity(&prepare_world(content, Some(dim.path())).unwrap()),
179 Some(0.25)
180 );
181 assert_eq!(key_intensity(&prepare_world(content, None).unwrap()), None);
183 }
184}