//! 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(); }