mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-12 13:33:19 +00:00
Scripts: a canceled request stops a sleeping or waiting run at its next slice
script_pump read vectorcraft_doc::cancel only after the guard that keeps a run waiting, so a run asleep in $.sleep went on waiting until its wake time (up to 1,000 ms), and a run waiting at a dialog or for approval to grow waited until someone answered. The check now runs before that guard, after the dialog-failure check, so a canceled request ends the run at the next slice and every document goes back.
This commit is contained in:
@@ -1787,6 +1787,10 @@ impl Session {
|
||||
if let Some(e) = job.host.dialogs.take_failure() {
|
||||
return Some(self.script_failed(job, &e));
|
||||
}
|
||||
// A canceled request ends a sleep or a wait at this pump.
|
||||
if vectorcraft_doc::cancel::check().is_err() {
|
||||
self.script_stop.set();
|
||||
}
|
||||
if (job.host.waiting.get() || job.host.asleep().is_some() || job.host.dialogs.is_waiting()) && !self.script_stop.is_set() {
|
||||
JOBS.with(|jobs| jobs.borrow_mut().insert(key.id, job));
|
||||
self.script_key = Some(key);
|
||||
@@ -1798,9 +1802,6 @@ impl Session {
|
||||
let e = RunError { kind: RunErrorKind::Limit(kind), message: format!("limit reached: {kind:?}"), line: 0, column: 0 };
|
||||
return Some(self.script_failed(job, &e));
|
||||
}
|
||||
if vectorcraft_doc::cancel::check().is_err() {
|
||||
self.script_stop.set();
|
||||
}
|
||||
job.host.calls_this_slice.set(0);
|
||||
let outcome = run_slice(self, &mut job);
|
||||
match outcome {
|
||||
|
||||
@@ -217,6 +217,26 @@ fn dialog_loop_stops_or_exhausts() {
|
||||
assert_eq!(art(&s), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_run_waiting_at_a_dialog_stops_at_the_next_slice_once_its_work_is_canceled() {
|
||||
let mut s = one_doc();
|
||||
let before = art(&s);
|
||||
let token = crate::cancel::CancelToken::new();
|
||||
s.script_frames = true;
|
||||
let source = "vc.run('shape.rectangle', {x: 20, y: 0, width: 5, height: 5}); alert('x'); vc.log('answered');";
|
||||
assert_eq!(s.execute("script.run", &json!({"source": source, "name": "d.js"})).unwrap()["running"], true);
|
||||
assert!(crate::cancel::scope(&token, None, || s.script_pump()).is_none());
|
||||
assert!(s.script_dialog().is_some());
|
||||
token.cancel();
|
||||
let ended = crate::cancel::scope(&token, None, || s.script_pump());
|
||||
assert!(ended.expect("the run goes on waiting for an answer").is_err());
|
||||
let report = s.script_report().unwrap();
|
||||
assert_eq!((report["kind"].as_str(), &report["log"]), (Some("stopped"), &json!([])), "{report}");
|
||||
assert_eq!(report["dialogs"][0]["answer"], "stop");
|
||||
assert_eq!(s.script_dialog(), None);
|
||||
assert_eq!(art(&s), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dialog_text_limits_and_controls() {
|
||||
let mut s = one_doc();
|
||||
|
||||
@@ -5,6 +5,7 @@ use std::time::Duration;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::cancel::{self, CancelToken};
|
||||
use crate::script::{TEST_ELAPSED, TEST_NO_CLOCK};
|
||||
use crate::*;
|
||||
|
||||
@@ -204,6 +205,36 @@ fn a_sleeping_run_stops_when_asked() {
|
||||
TEST_ELAPSED.set(None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_sleeping_run_stops_at_the_next_slice_once_its_work_is_canceled() {
|
||||
for (asleep_until, when) in
|
||||
[(None, "before its first slice"), (Some(Duration::ZERO), "while it sleeps"), (Some(Duration::from_secs(1)), "at its wake time")]
|
||||
{
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
let before = (format!("{:?}", s.documents()[0].doc.layers), s.documents()[0].history.undo.len());
|
||||
s.script_frames = true;
|
||||
TEST_ELAPSED.set(Some(Duration::ZERO));
|
||||
let source = "vc.run('shape.rectangle', {x: 0, y: 0, width: 5, height: 5}); $.sleep(1000); vc.log('woke'); \
|
||||
vc.run('shape.rectangle', {x: 9, y: 0, width: 5, height: 5});";
|
||||
s.execute("script.run", &json!({"source": source, "name": "t.js"})).unwrap();
|
||||
let token = CancelToken::new();
|
||||
if let Some(at) = asleep_until {
|
||||
assert!(cancel::scope(&token, None, || s.script_pump()).is_none(), "{when}");
|
||||
assert_eq!(s.script_sleep_left(), Some(Duration::from_secs(1)), "{when}");
|
||||
TEST_ELAPSED.set(Some(at));
|
||||
}
|
||||
token.cancel();
|
||||
let ended = cancel::scope(&token, None, || s.script_pump());
|
||||
TEST_ELAPSED.set(None);
|
||||
assert!(ended.unwrap_or_else(|| panic!("{when}: the run goes on waiting")).is_err(), "{when}");
|
||||
let report = s.script_report().unwrap();
|
||||
assert_eq!((report["kind"].as_str(), &report["log"]), (Some("stopped"), &json!([])), "{when}: {report}");
|
||||
assert!(!s.script_running(), "{when}");
|
||||
assert_eq!((format!("{:?}", s.documents()[0].doc.layers), s.documents()[0].history.undo.len()), before, "{when}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sleep_takes_milliseconds_from_0_to_1000() {
|
||||
let mut s = Session::new();
|
||||
|
||||
@@ -845,6 +845,30 @@ fn a_person_can_let_a_growing_run_go_on_or_stop_it() {
|
||||
assert_eq!(state(&s), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_run_waiting_to_grow_stops_at_the_next_slice_once_its_work_is_canceled() {
|
||||
TEST_GROWTH.with(|g| g.set(Some(20)));
|
||||
let mut s = two_docs();
|
||||
let before = state(&s);
|
||||
s.script_frames = true;
|
||||
run(&mut s, "for (var i = 0; i < 25; i++) vc.run('shape.rectangle', {x:i,y:1,width:1,height:1});").unwrap();
|
||||
let token = crate::cancel::CancelToken::new();
|
||||
for _ in 0..1_000 {
|
||||
if s.script_waiting() {
|
||||
break;
|
||||
}
|
||||
assert!(crate::cancel::scope(&token, None, || s.script_pump()).is_none());
|
||||
}
|
||||
assert!(s.script_waiting());
|
||||
token.cancel();
|
||||
let ended = crate::cancel::scope(&token, None, || s.script_pump());
|
||||
TEST_GROWTH.with(|g| g.set(None));
|
||||
assert!(ended.expect("the run goes on waiting for Continue").is_err());
|
||||
assert_eq!(s.script_report().unwrap()["kind"], "stopped");
|
||||
assert!(!s.script_running());
|
||||
assert_eq!(state(&s), before);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_file_saved_before_a_failure_stays_written_and_is_named() {
|
||||
let path = std::env::temp_dir().join(format!("vc-script-save-{}.svg", std::process::id()));
|
||||
|
||||
+1
-1
@@ -1742,7 +1742,7 @@ VectorCraft's own, and `vc.api.has("dollar")` is true where they are available:
|
||||
| `localize` | `false`; while `true`, `String(object)` of a localization object gives its text for `$.locale` |
|
||||
| `os` | `Windows`, `macOS`, `Linux`, `FreeBSD`, `Unix` or `Web`, without a version |
|
||||
| `screens` | an empty array |
|
||||
| `sleep(ms)` | waits from 0 to 1,000 milliseconds between two frames of the app, without stopping it from drawing; `script.stop` ends the wait; it does not wait on the web; called with more than 0 inside a getter, a conversion, a callback of a built-in function or a file `$.evalFile` runs, it throws an `Error` instead of waiting |
|
||||
| `sleep(ms)` | waits from 0 to 1,000 milliseconds between two frames of the app, without stopping it from drawing; `script.stop` ends the wait, and so does `notifications/cancelled` for a run its `tools/call` waits for, without waiting for the time to pass; it does not wait on the web; called with more than 0 inside a getter, a conversion, a callback of a built-in function or a file `$.evalFile` runs, it throws an `Error` instead of waiting |
|
||||
| `strict` | `false` |
|
||||
|
||||
Members that are values (`build`, `level`, `os` and the like) cannot be changed. A wrong value for a member that can
|
||||
|
||||
@@ -740,6 +740,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- Scripts: File › Scripts › Other Script… and `script.run` run a JavaScript file against the open documents as one transaction with one undo step for each changed document. A failed or stopped run puts every document back. `crates/engine/src/tests/script_run.rs`.
|
||||
- Scripts: Window › Script Console shows each run's `$.write` and `$.writeln` output, `vc.log` entries, uncaught error with the script's name, line and column, and a report of its time, host calls and slowest commands, keeping at most 2,048 lines and counting the lines it dropped. `script.run`'s report carries the same data, and `script.console`, `script.console.clear` and `ui.copyScriptConsole` read, clear and copy it. `crates/engine/src/tests/script_console.rs`.
|
||||
- Scripts: the `$` object has the other members the Dollar object page lists (https://extendscript.docsforadobe.dev/extendscript-tools-features/dollar-object/), from `$.about()` to `$.version`, and scripts have the global `localize(object, ...values)` (https://extendscript.docsforadobe.dev/extendscript-tools-features/localizing-extendscript-strings/), `toSource(value)` and `Object.prototype.toSource()`. Each run starts with the interface's locale, and what a script changes in `$` lasts only for that run. `crates/engine/src/tests/script_dollar.rs`, `crates/js/src/extension_tests.rs`.
|
||||
- Scripts: a canceled request stops a run that sleeps in `$.sleep`, waits at a dialog or waits for approval to grow at the run's next slice, before its wake time or an answer, and every document goes back to its state before the run. VectorCraft's own rule. `crates/engine/src/tests/script_dollar.rs`, `crates/engine/src/tests/script_dialogs.rs`, `crates/engine/src/tests/script_run.rs`.
|
||||
- Scripts: the Script Console panel's report line reads as `script.console` gives it, `time unavailable` included on a platform without a clock; counts of host calls, dropped lines and cut lines take the singular for one, in each language's own plural forms; and a run that is no longer available still gets its end line (`crates/engine/src/tests/script_console.rs`, `crates/ui-egui/src/panels/tests_script_console.rs`).
|
||||
- Scripts: an error `$.evalFile` throws for a file it cannot run is reported at that file's line and column only while no `catch` has taken it. Caught and thrown again later, in the script or in another file, it is reported where it was thrown again, and a new error whose message holds its text is reported where that error was thrown. VectorCraft's own rule. `crates/engine/src/tests/script_include.rs`, `crates/js/src/host_tests.rs`.
|
||||
- Scripts: an uncaught error that one or more `finally` blocks pass on unchanged, in the script or in a function it called, is reported at the line and column where it was thrown, in `script.run`'s report and the Script Console. An error a `finally` block throws is reported at that throw, and one its `return`, `break` or `continue` discards is not reported. VectorCraft's own rule. `crates/engine/src/tests/script_include.rs`, `crates/js/src/host_tests.rs`.
|
||||
|
||||
Reference in New Issue
Block a user