BlueGit

golden-age paperdoll-renderer

Files

paperdoll-renderer / examples / resume_fixture.rs

resume_fixture.rs

resume_fixture.rs 8.6 KiB Download Raw
1//! Real native GPU proof for production resume (no input automation, no delays).
2//! Usage:
3//! resume_fixture setup ROOT MALE.glb FEMALE.glb WIDTH HEIGHT FPS
4//! resume_fixture render ROOT male|female [--cancel-after=N] [--abort-after=N]
5//! resume_fixture verify ROOT (ExportRun::open on every run)
6//! resume_fixture resume ROOT male|female [--cancel-after=N] [--screenshot=PATH]
7//! `--abort-after` kills the process with `abort()` (a real crash: no terminal
8//! marker). Each GUI invocation writes ROOT/<mode>-result.json.
9use eframe::App;
10use paperdoll_renderer::{
11 editor::EditorApp,
12 session::{adopt_file, Session, Sex},
13};
14use std::path::PathBuf;
15
16struct Fixture {
17 editor: EditorApp,
18 root: PathBuf,
19 mode: String,
20 cancel_after: Option<u32>,
21 abort_after: Option<u32>,
22 screenshot: Option<PathBuf>,
23 phase: u8,
24 cancel_sent: bool,
25 frames: u64,
26 last: u32,
27 done: bool,
28 candidates: serde_json::Value,
29}
30
31impl App for Fixture {
32 fn update(&mut self, ctx: &eframe::egui::Context, frame: &mut eframe::Frame) {
33 self.frames += 1;
34 self.editor.update(ctx, frame);
35 match self.phase {
36 0 => {
37 if self.mode == "resume" {
38 self.editor.scan_resumable_runs();
39 self.candidates = serde_json::Value::Array(
40 self.editor
41 .resumable_runs()
42 .iter()
43 .map(|c| {
44 serde_json::json!({"name":c.name,"state":c.state,"journaled":c.journaled,"planned":c.planned,"blocked":c.blocked})
45 })
46 .collect(),
47 );
48 println!("CANDIDATES {}", self.candidates);
49 self.phase = 1;
50 } else {
51 self.editor.render_all_angles();
52 self.phase = 3;
53 }
54 ctx.request_repaint();
55 return;
56 }
57 1 => {
58 if self.screenshot.is_some() {
59 ctx.send_viewport_cmd(eframe::egui::ViewportCommand::Screenshot);
60 self.phase = 2;
61 ctx.request_repaint();
62 return;
63 }
64 self.start_resume(ctx);
65 return;
66 }
67 2 => {
68 let shot = ctx.input(|i| {
69 i.raw.events.iter().find_map(|e| match e {
70 eframe::egui::Event::Screenshot { image, .. } => Some(image.clone()),
71 _ => None,
72 })
73 });
74 if let (Some(image), Some(path)) = (shot, &self.screenshot) {
75 let [w, h] = image.size;
76 let rgba: Vec<u8> = image.pixels.iter().flat_map(|p| p.to_array()).collect();
77 let file = std::fs::File::create(path).unwrap();
78 let mut enc =
79 png::Encoder::new(std::io::BufWriter::new(file), w as u32, h as u32);
80 enc.set_color(png::ColorType::Rgba);
81 enc.set_depth(png::BitDepth::Eight);
82 enc.write_header().unwrap().write_image_data(&rgba).unwrap();
83 println!("SCREENSHOT {}", path.display());
84 self.start_resume(ctx);
85 }
86 ctx.request_repaint();
87 return;
88 }
89 _ => {}
90 }
91 let (completed, total, label) = self.editor.render_progress();
92 if completed != self.last {
93 println!("PASS {completed}/{total}: {label}");
94 self.last = completed;
95 }
96 if let Some(n) = self.abort_after {
97 if completed >= n {
98 println!("ABORT at {completed}/{total}");
99 std::process::abort();
100 }
101 }
102 if let Some(n) = self.cancel_after {
103 if completed >= n && !self.cancel_sent && self.editor.render_is_running() {
104 self.cancel_sent = true;
105 println!("CANCEL at {completed}/{total}");
106 self.editor.request_render_cancel();
107 }
108 }
109 if !self.editor.render_is_running() && !self.done {
110 let error = self.editor.render_error().map(str::to_string);
111 self.finish(ctx, error);
112 }
113 ctx.request_repaint();
114 }
115 fn on_exit(&mut self, gl: Option<&glow::Context>) {
116 self.editor.on_exit(gl);
117 }
118}
119
120impl Fixture {
121 fn start_resume(&mut self, ctx: &eframe::egui::Context) {
122 self.phase = 3;
123 let Some(dir) = self
124 .editor
125 .resumable_runs()
126 .iter()
127 .find(|c| c.blocked.is_none())
128 .map(|c| c.dir.clone())
129 else {
130 self.finish(ctx, Some("geen hervatbare run".into()));
131 return;
132 };
133 self.editor.resume_run(dir);
134 }
135 fn finish(&mut self, ctx: &eframe::egui::Context, error: Option<String>) {
136 self.done = true;
137 let (completed, total, label) = self.editor.render_progress();
138 let value = serde_json::json!({
139 "mode": self.mode,
140 "completed": completed,
141 "total": total,
142 "last_label": label,
143 "cancel_sent": self.cancel_sent,
144 "error": error,
145 "candidates_before": self.candidates,
146 "gui_updates": self.frames,
147 });
148 std::fs::write(
149 self.root.join(format!("{}-result.json", self.mode)),
150 serde_json::to_vec_pretty(&value).unwrap(),
151 )
152 .unwrap();
153 println!("RESULT {value}");
154 ctx.send_viewport_cmd(eframe::egui::ViewportCommand::Close);
155 }
156}
157
158fn main() -> Result<(), Box<dyn std::error::Error>> {
159 let a = std::env::args().skip(1).collect::<Vec<_>>();
160 let mode = a.first().ok_or("mode")?.clone();
161 let root = PathBuf::from(a.get(1).ok_or("ROOT")?);
162 if mode == "verify" {
163 // Open every run directory through the strict schema-3 export_set reader.
164 let mut dirs = std::fs::read_dir(root.join("renders"))?
165 .map(|e| e.map(|e| e.path()))
166 .collect::<Result<Vec<_>, _>>()?;
167 dirs.sort();
168 for dir in dirs {
169 match paperdoll_renderer::export_set::ExportRun::open(&dir) {
170 Ok(run) => println!(
171 "OPEN-OK {} png_files={}",
172 dir.display(),
173 run.complete.png_files
174 ),
175 Err(e) => println!("OPEN-REJECTED {}: {e}", dir.display()),
176 }
177 }
178 return Ok(());
179 }
180 if mode == "setup" {
181 let mut s = Session::new();
182 s.install_new_project(&root)?;
183 for (sex, index) in [(Sex::Male, 2), (Sex::Female, 3)] {
184 let path = adopt_file(&root, &PathBuf::from(&a[index]), "assets/body")?;
185 s.set_body_path(sex, path);
186 }
187 s.set_render(a[4].parse()?, a[5].parse()?, a[6].parse()?);
188 s.save()?;
189 return Ok(());
190 }
191 let sex = match a.get(2).map(String::as_str) {
192 Some("male") => Sex::Male,
193 Some("female") => Sex::Female,
194 _ => return Err("male|female".into()),
195 };
196 let flag = |name: &str| {
197 a.iter()
198 .find_map(|v| v.strip_prefix(name))
199 .map(|v| v.to_string())
200 };
201 let cancel_after = flag("--cancel-after=").map(|v| v.parse()).transpose()?;
202 let abort_after = flag("--abort-after=").map(|v| v.parse()).transpose()?;
203 let screenshot = flag("--screenshot=").map(PathBuf::from);
204 let root2 = root.clone();
205 let mode2 = mode.clone();
206 eframe::run_native(
207 "Paperdoll Resume Fixture",
208 eframe::NativeOptions {
209 viewport: eframe::egui::ViewportBuilder::default().with_inner_size([1150., 820.]),
210 ..paperdoll_renderer::preview_gl::native_options()
211 },
212 Box::new(move |_| {
213 let mut editor = EditorApp::default();
214 editor.open_path(root2.join("character.ppd"));
215 editor.open_body_configuration(sex);
216 Ok(Box::new(Fixture {
217 editor,
218 root: root2,
219 mode: mode2,
220 cancel_after,
221 abort_after,
222 screenshot,
223 phase: 0,
224 cancel_sent: false,
225 frames: 0,
226 last: 0,
227 done: false,
228 candidates: serde_json::Value::Null,
229 }))
230 }),
231 )?;
232 let report: serde_json::Value =
233 serde_json::from_slice(&std::fs::read(root.join(format!("{mode}-result.json")))?)?;
234 println!("FINAL {report}");
235 Ok(())
236}