Skip to main content

pfx_editor/
pgpu.rs

1use std::ffi::OsStr;
2
3#[derive(Clone, Debug, Default, PartialEq, Eq)]
4pub struct Status {
5    pub busy: Option<u32>,
6    pub jobs: usize,
7    pub running: Option<String>,
8    pub play: Option<String>,
9    pub paused: bool,
10}
11
12impl Status {
13    pub fn parse(json: &str) -> Result<Status, String> {
14        let value: serde_json::Value = serde_json::from_str(json).map_err(|e| e.to_string())?;
15        Ok(Status::of(&value))
16    }
17
18    pub fn of(value: &serde_json::Value) -> Status {
19        let jobs = value
20            .get("jobs")
21            .and_then(serde_json::Value::as_array)
22            .cloned()
23            .unwrap_or_default();
24        let running = jobs
25            .iter()
26            .find(|job| job.get("state").and_then(serde_json::Value::as_str) == Some("running"))
27            .map(|job| {
28                let name = job
29                    .get("as")
30                    .and_then(serde_json::Value::as_str)
31                    .unwrap_or("?");
32                let label = job
33                    .get("label")
34                    .and_then(serde_json::Value::as_str)
35                    .unwrap_or("");
36                format!("{name}: {label}")
37            });
38        let play = match value.get("play") {
39            Some(serde_json::Value::Object(play)) => Some(
40                play.get("name")
41                    .and_then(serde_json::Value::as_str)
42                    .unwrap_or("a game")
43                    .to_string(),
44            ),
45            _ => None,
46        };
47        Status {
48            busy: value
49                .get("gpu_busy")
50                .and_then(serde_json::Value::as_u64)
51                .map(|busy| busy as u32),
52            jobs: jobs.len(),
53            running,
54            play,
55            paused: value
56                .get("paused")
57                .and_then(serde_json::Value::as_bool)
58                .unwrap_or(false),
59        }
60    }
61
62    pub fn line(&self) -> String {
63        let mut parts = Vec::new();
64        if let Some(busy) = self.busy {
65            parts.push(format!("gpu {busy}%"));
66        }
67        parts.push(format!("{} queued", self.jobs));
68        if let Some(play) = &self.play {
69            parts.push(format!("playing {play}"));
70        }
71        if self.paused {
72            parts.push("queue paused".to_string());
73        }
74        if let Some(running) = &self.running {
75            parts.push(running.clone());
76        }
77        parts.join(" · ")
78    }
79}
80
81pub fn status() -> Result<Status, String> {
82    pfx_editor_shell::pgpu::fetch()
83        .map(|value| Status::of(&value))
84        .ok_or_else(|| "pgpu status: no answer".to_string())
85}
86
87pub fn on_path(path: &OsStr) -> bool {
88    let program = format!("pgpu{}", std::env::consts::EXE_SUFFIX);
89    std::env::split_paths(path).any(|folder| folder.join(&program).is_file())
90}
91
92#[cfg(test)]
93mod tests {
94    use std::path::Path;
95
96    use super::*;
97
98    #[test]
99    fn the_status_line_names_the_load_the_queue_and_a_game() {
100        let json = r#"{"v":1,"paused":false,"gpu_busy":42,"play":{"pid":7,"name":"pfx edit: room.scene.toml"},
101            "jobs":[{"pid":1,"state":"running","as":"pfx","label":"cargo test"},{"pid":2,"state":"yielded","as":"manf","label":"x"}]}"#;
102        let status = Status::parse(json).unwrap();
103        assert_eq!(status.busy, Some(42));
104        assert_eq!(status.jobs, 2);
105        assert_eq!(
106            status.line(),
107            "gpu 42% · 2 queued · playing pfx edit: room.scene.toml · pfx: cargo test"
108        );
109        let idle = Status::parse(r#"{"v":1,"paused":true,"play":null,"jobs":[]}"#).unwrap();
110        assert_eq!(idle.line(), "0 queued · queue paused");
111        assert!(Status::parse("not json").is_err());
112    }
113
114    #[test]
115    fn the_hook_is_on_only_when_pgpu_is_found_on_the_path() {
116        let path = crate::tests::scene_copy("pgpu on path");
117        let folder = path.parent().unwrap().to_path_buf();
118        let empty = folder.join("empty");
119        let tools = folder.join("tools");
120        std::fs::create_dir_all(&empty).unwrap();
121        std::fs::create_dir_all(&tools).unwrap();
122        std::fs::write(
123            tools.join(format!("pgpu{}", std::env::consts::EXE_SUFFIX)),
124            "",
125        )
126        .unwrap();
127        let joined = |folders: &[&Path]| std::env::join_paths(folders).unwrap();
128        assert!(!on_path(OsStr::new("")));
129        assert!(!on_path(&joined(&[&empty])));
130        assert!(on_path(&joined(&[&empty, &tools])));
131        assert!(!on_path(&joined(&[&tools.join("pgpu")])));
132    }
133}