hlsl_snapshots/
lib.rs

1use std::{error::Error, fmt::Display, fs, io, path::Path};
2
3use anyhow::{anyhow, ensure};
4use nanoserde::{self, DeRon, DeRonErr, SerRon};
5
6#[derive(Debug)]
7struct BadRonParse(BadRonParseKind);
8
9impl Display for BadRonParse {
10    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11        write!(f, "failed to read RON configuration of HLSL snapshot test")
12    }
13}
14
15impl Error for BadRonParse {
16    fn source(&self) -> Option<&(dyn Error + 'static)> {
17        Some(&self.0)
18    }
19}
20
21#[derive(Debug)]
22enum BadRonParseKind {
23    Read { source: io::Error },
24    Parse { source: DeRonErr },
25    Empty,
26}
27
28impl Display for BadRonParseKind {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        match self {
31            BadRonParseKind::Read { source } => Display::fmt(source, f),
32            BadRonParseKind::Parse { source } => Display::fmt(source, f),
33            BadRonParseKind::Empty => write!(f, "no configuration was specified"),
34        }
35    }
36}
37
38impl Error for BadRonParseKind {
39    fn source(&self) -> Option<&(dyn Error + 'static)> {
40        match self {
41            BadRonParseKind::Read { source } => source.source(),
42            BadRonParseKind::Parse { source } => source.source(),
43            BadRonParseKind::Empty => None,
44        }
45    }
46}
47
48#[derive(Debug, DeRon, SerRon)]
49pub struct Config {
50    pub vertex: Vec<ConfigItem>,
51    pub fragment: Vec<ConfigItem>,
52    pub compute: Vec<ConfigItem>,
53    pub task: Vec<ConfigItem>,
54    pub mesh: Vec<ConfigItem>,
55}
56
57impl Config {
58    #[must_use]
59    pub fn empty() -> Self {
60        Self {
61            vertex: Default::default(),
62            fragment: Default::default(),
63            compute: Default::default(),
64            task: Default::default(),
65            mesh: Default::default(),
66        }
67    }
68
69    pub fn from_path(path: impl AsRef<Path>) -> anyhow::Result<Config> {
70        let path = path.as_ref();
71        let raw_config = fs::read_to_string(path)
72            .map_err(|source| BadRonParse(BadRonParseKind::Read { source }))?;
73        let config = Config::deserialize_ron(&raw_config)
74            .map_err(|source| BadRonParse(BadRonParseKind::Parse { source }))?;
75        ensure!(!config.is_empty(), BadRonParse(BadRonParseKind::Empty));
76        Ok(config)
77    }
78
79    pub fn to_file(&self, path: impl AsRef<Path>) -> anyhow::Result<()> {
80        let path = path.as_ref();
81        let mut s = self.serialize_ron();
82        s.push('\n');
83        fs::write(path, &s).map_err(|e| anyhow!("failed to write to {}: {e}", path.display()))
84    }
85
86    #[must_use]
87    pub fn is_empty(&self) -> bool {
88        let Self {
89            vertex,
90            fragment,
91            compute,
92            task,
93            mesh,
94        } = self;
95        vertex.is_empty()
96            && fragment.is_empty()
97            && compute.is_empty()
98            && task.is_empty()
99            && mesh.is_empty()
100    }
101}
102
103#[derive(Debug, DeRon, SerRon)]
104pub struct ConfigItem {
105    pub entry_point: String,
106    /// See also
107    /// <https://learn.microsoft.com/en-us/windows/win32/direct3dtools/dx-graphics-tools-fxc-using>.
108    pub target_profile: String,
109}