Skip to main content

pfx_bench/
args.rs

1use std::path::PathBuf;
2
3use pfx_core::cli;
4
5pub const DEFAULT_SIZE: (u32, u32) = (1920, 1080);
6
7const FLAGS: &[&str] = &[
8    "--seconds",
9    "--camera",
10    "--replay",
11    "--size",
12    "--stats",
13    "--seed",
14];
15
16#[derive(Clone, Debug, PartialEq)]
17pub struct Plan {
18    pub scene: PathBuf,
19    pub seconds: f64,
20    pub camera: Option<String>,
21    pub replay: Option<PathBuf>,
22    pub size: Option<(u32, u32)>,
23    pub stats: Option<PathBuf>,
24    pub seed: u64,
25}
26
27fn size(text: &str) -> Result<(u32, u32), String> {
28    let wrong = || cli::invalid(text, "--size", "expected WxH between 1 and 16384");
29    let (width, height) = text.split_once(['x', 'X']).ok_or_else(wrong)?;
30    let number = |part: &str| {
31        part.parse::<u32>()
32            .ok()
33            .filter(|value| (1..=16_384).contains(value))
34            .ok_or_else(wrong)
35    };
36    Ok((number(width)?, number(height)?))
37}
38
39pub fn parse(args: &[String]) -> Result<Plan, String> {
40    let mut scene = None;
41    let mut seconds = None;
42    let mut camera = None;
43    let mut replay = None;
44    let mut size_flag = None;
45    let mut stats = None;
46    let mut seed = 0;
47    let mut seen = Vec::new();
48    let mut words = args.iter();
49    while let Some(word) = words.next() {
50        let (flag, inline) = match word.split_once('=') {
51            Some((flag, value)) if flag.starts_with("--") => (flag, Some(value.to_string())),
52            _ => (word.as_str(), None),
53        };
54        if !flag.starts_with('-') {
55            if scene.is_some() {
56                return Err(cli::unexpected(word));
57            }
58            scene = Some(PathBuf::from(word));
59            continue;
60        }
61        if !FLAGS.contains(&flag) {
62            return Err(cli::unexpected(flag));
63        }
64        if seen.contains(&flag) {
65            return Err(cli::repeated(&cli::with_value(flag)));
66        }
67        seen.push(flag);
68        let value = match inline {
69            Some(value) => value,
70            None => words
71                .next()
72                .cloned()
73                .ok_or_else(|| cli::missing_value(flag))?,
74        };
75        match flag {
76            "--seconds" => {
77                let parsed = value
78                    .parse::<f64>()
79                    .ok()
80                    .filter(|seconds| seconds.is_finite() && *seconds > 0.0)
81                    .ok_or_else(|| cli::invalid(&value, flag, "expected a number above 0"))?;
82                seconds = Some(parsed);
83            }
84            "--camera" => camera = Some(value),
85            "--replay" => replay = Some(PathBuf::from(value)),
86            "--size" => size_flag = Some(size(&value)?),
87            "--stats" => stats = Some(PathBuf::from(value)),
88            _ => {
89                seed = value
90                    .parse::<u64>()
91                    .map_err(|error| cli::invalid(&value, flag, error))?;
92            }
93        }
94    }
95    let mut missing = Vec::new();
96    if seconds.is_none() {
97        missing.push(cli::with_value("--seconds"));
98    }
99    if scene.is_none() {
100        missing.push("<SCENE>".to_string());
101    }
102    if !missing.is_empty() {
103        return Err(cli::required(&missing));
104    }
105    Ok(Plan {
106        scene: scene.unwrap_or_default(),
107        seconds: seconds.unwrap_or_default(),
108        camera,
109        replay,
110        size: size_flag,
111        stats,
112        seed,
113    })
114}