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.
119 lines
4.9 KiB
Rust
119 lines
4.9 KiB
Rust
//! 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();
|
||
}
|