Files
OneCAD/crates/ui-egui/src/apply_off_thread_tests.rs
T
phanoob 9935133b56 Live preview: the tool alone, the boolean only on OK, computed off the window thread (#1201)
The preview builds only the tool; the boolean runs once on OK, off the window thread, with a Computing label while it takes a while. Follow-up to #1020.
2026-10-14 19:31:29 -04:00

119 lines
4.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! OK off the window thread: a feature that takes longer than the app's wait is computed beside
//! the window, the dialog saying so, and applied once it is in; Cancel drops it.
use std::time::Duration;
use serde_json::json;
use crate::scenario::Harness;
/// A 60 × 20 × 10 block and, just finished on its top, a 10 × 10 square: Extrude takes it.
fn square_on_a_block() -> Harness {
let mut h = Harness::new();
h.app.home.open = false;
for (id, p) in [
("sketch.create", json!({"plane": "XY"})),
("sketch.rectangle.two_point", json!({"p0": [0, 0], "p1": [60, 20]})),
("sketch.finish", json!({})),
("solid.extrude", json!({"distance": 10})),
("sketch.create", json!({"plane": {"origin": [0, 0, 10], "x_dir": [1, 0, 0], "y_dir": [0, 1, 0]}})),
("sketch.rectangle.two_point", json!({"p0": [5, 5], "p1": [15, 15]})),
] {
h.app.run(id, p).unwrap();
}
h.app.finish_sketch();
h.app.start("solid.extrude");
h.frames(2);
h
}
/// Frames until OK's evaluation is in and applied (a few seconds at most).
fn until_applied(h: &mut Harness) {
for _ in 0..500 {
if h.app.applying.is_none() {
break;
}
h.frames(1);
std::thread::sleep(Duration::from_millis(10));
}
h.frames(1);
}
#[test]
fn ok_computes_off_the_window_thread_saying_so_then_applies() {
let mut h = square_on_a_block();
h.app.apply_off_thread = Some(Duration::ZERO);
h.step(&json!({"key": "Enter"})).unwrap();
assert!(h.app.applying.is_some() && h.app.dialog.is_some(), "computing, the dialog open");
// The evaluation may be in by the next frame: it is held until the dialog has been seen
// saying so (the evaluation itself is in the cache all the same).
let (finish, done) = std::sync::mpsc::channel();
if let Some(job) = h.app.applying.as_mut() {
let _ = job.done.recv();
job.done = done;
}
h.frames(1);
assert!(h.widget("Computing… 0 s").is_some(), "the dialog says it computes");
// Enter again does not start it twice.
h.step(&json!({"key": "Enter"})).unwrap();
finish.send(()).unwrap();
until_applied(&mut h);
assert!(h.app.dialog.is_none(), "{:?}", h.app.dialog.as_ref().and_then(|d| d.error.clone()));
h.step(&json!({"expect": {"bodies": 1, "volume": 13000}})).unwrap();
assert_eq!(h.app.session.doc.features.len(), 4, "one extrude added");
}
#[test]
fn cancel_while_computing_applies_nothing() {
let mut h = square_on_a_block();
h.app.apply_off_thread = Some(Duration::ZERO);
let features = h.app.session.doc.features.len();
h.step(&json!({"key": "Enter"})).unwrap();
assert!(h.app.applying.is_some());
h.step(&json!({"key": "Escape"})).unwrap();
assert!(h.app.applying.is_none() && h.app.dialog.is_none());
h.frames(5);
assert_eq!(h.app.session.doc.features.len(), features);
}
#[test]
fn the_dialog_holds_its_inputs_while_ok_computes() {
let mut h = square_on_a_block();
h.app.fit_view();
h.frames(2);
let picked = h.app.dialog.as_ref().map(|d| d.inputs[0].items.clone()).unwrap();
// OK computing, until the test lets it finish.
let (finish, done) = std::sync::mpsc::channel();
let cmds = h.app.dialog.as_ref().and_then(|d| crate::dialogs::apply_commands(&h.app, d).ok()).unwrap();
h.app.applying = Some(crate::dialogs::Applying { done, started: crate::now_ms(), cmds });
// A click on the square picked would take it out of the profiles.
h.step(&json!({"click": {"world": [10, 10, 10]}})).unwrap();
h.frames(2);
assert_eq!(h.app.dialog.as_ref().map(|d| d.inputs[0].items.clone()), Some(picked), "no pick while computing");
finish.send(()).unwrap();
until_applied(&mut h);
assert!(h.app.dialog.is_none());
h.step(&json!({"expect": {"bodies": 1, "volume": 13000}})).unwrap();
}
/// Inputs that changed while OK computed (nothing should change them) are computed in turn, off
/// the window thread too, then applied: run as OK finished, they would hold the window for the
/// whole boolean.
#[test]
fn inputs_changed_while_ok_computes_are_computed_off_the_thread_too() {
let mut h = square_on_a_block();
h.app.apply_off_thread = Some(Duration::ZERO);
let features = h.app.session.doc.features.len();
// OK computing other commands than the dialog's, until the test lets it finish.
let (finish, done) = std::sync::mpsc::channel();
h.app.applying = Some(crate::dialogs::Applying { done, started: crate::now_ms(), cmds: Vec::new() });
finish.send(()).unwrap();
h.frames(1);
assert!(h.app.applying.is_some(), "the dialog's own commands are computing");
assert_eq!(h.app.session.doc.features.len(), features, "nothing applied on the window thread");
until_applied(&mut h);
assert!(h.app.dialog.is_none());
assert_eq!(h.app.session.doc.features.len(), features + 1);
h.step(&json!({"expect": {"bodies": 1, "volume": 13000}})).unwrap();
}