Skip to main content

concinnity_world/
source_args.rs

1//! JSON-args source selection for the shader-backed asset types.
2//!
3//! A Shader stage / SdfVolume declares its shader source as a single path plus
4//! an optional per-platform map; the build pipeline and the world checks pick
5//! the building backend's entry straight from the raw args JSON. The runtime
6//! selects from the typed struct instead (`StageSourceExt` and the SdfVolume
7//! clamp in concinnity-core), so the runtime tier carries no JSON parsing.
8
9use concinnity_core::platform::Platform;
10
11// Resolve a shader stage source filename for `platform` from its raw stage args:
12// the `sources` map entry for the platform wins, then the single `source`
13// path when its file extension matches the platform.
14pub(crate) fn stage_source_path(args: &serde_json::Value, platform: Platform) -> Option<String> {
15    if let Some(obj) = args.get("sources").and_then(|v| v.as_object())
16        && let Some(src) = obj.get(platform.key()).and_then(|v| v.as_str())
17    {
18        return Some(src.to_string());
19    }
20    let src = args
21        .get("source")
22        .and_then(|v| v.as_str())
23        .filter(|s| !s.is_empty())?;
24    let ext = std::path::Path::new(src)
25        .extension()
26        .and_then(|e| e.to_str())
27        .unwrap_or("");
28    if platform.accepts_ext(ext) {
29        Some(src.to_string())
30    } else {
31        None
32    }
33}
34
35/// Resolves the shader source filename for the current platform from raw
36/// stage args (a `Shader` stage sub-object or an SdfVolume).
37pub fn resolve_source_from_args(args: &serde_json::Value) -> Option<String> {
38    stage_source_path(args, Platform::current())
39}
40
41// Resolve an SdfVolume's fragment shader path for `platform` from its raw
42// args: the `fragment_shaders` map entry wins, then the single
43// `fragment_shader` path when its extension matches the platform.
44pub(crate) fn sdf_volume_source_path(
45    args: &serde_json::Value,
46    platform: Platform,
47) -> Option<String> {
48    if let Some(obj) = args.get("fragment_shaders").and_then(|v| v.as_object())
49        && let Some(src) = obj
50            .get(platform.key())
51            .and_then(|v| v.as_str())
52            .filter(|s| !s.is_empty())
53    {
54        return Some(src.to_string());
55    }
56    let src = args
57        .get("fragment_shader")
58        .and_then(|v| v.as_str())
59        .filter(|s| !s.is_empty())?;
60    let ext = std::path::Path::new(src)
61        .extension()
62        .and_then(|e| e.to_str())
63        .unwrap_or("");
64    if platform.accepts_ext(ext) {
65        Some(src.to_string())
66    } else {
67        None
68    }
69}
70
71/// Resolve the raw fragment shader source an SdfVolume declares for the
72/// current build backend.
73pub fn current_platform_source_arg(args: &serde_json::Value) -> Option<String> {
74    sdf_volume_source_path(args, Platform::current())
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use serde_json::json;
81
82    #[test]
83    fn shader_source_path_selects_per_platform() {
84        // A sources-map entry for the requested platform wins.
85        let args = json!({"sources": {"metal": "a.metal", "hlsl": "a.hlsl", "glsl": "a.glsl"}});
86        assert_eq!(
87            stage_source_path(&args, Platform::Metal),
88            Some("a.metal".to_string())
89        );
90        assert_eq!(
91            stage_source_path(&args, Platform::Hlsl),
92            Some("a.hlsl".to_string())
93        );
94        assert_eq!(
95            stage_source_path(&args, Platform::Glsl),
96            Some("a.glsl".to_string())
97        );
98
99        // A single `source` is accepted when its extension matches the
100        // platform, rejected when it is another backend's shader extension.
101        let metal_only = json!({"source": "s.metal"});
102        assert_eq!(
103            stage_source_path(&metal_only, Platform::Metal),
104            Some("s.metal".to_string())
105        );
106        assert_eq!(stage_source_path(&metal_only, Platform::Hlsl), None);
107
108        // No source at all -> None.
109        assert_eq!(
110            stage_source_path(&json!({"kind": "vertex"}), Platform::Metal),
111            None
112        );
113    }
114
115    #[test]
116    fn sdf_source_path_prefers_map_over_single() {
117        let args = json!({
118            "fragment_shader": "shaders/single.metal",
119            "fragment_shaders": { "metal": "shaders/from_map.metal" },
120        });
121        assert_eq!(
122            sdf_volume_source_path(&args, Platform::Metal).as_deref(),
123            Some("shaders/from_map.metal")
124        );
125    }
126}