Merge pull request #781 from vangel76/script-773-repeating-tasks

#773: run repeating app.scheduleTask tasks until cancelTask
This commit is contained in:
Brandon Thomas
2026-10-12 03:03:45 -04:00
13 changed files with 668 additions and 57 deletions
+32 -27
View File
@@ -65,18 +65,18 @@ pub(crate) struct Active {
pub(crate) output: Vec<String>,
/// Open undo groups: (name, project snapshot when the outermost group began).
pub(crate) groups: Vec<(String, Arc<Project>)>,
/// `app.scheduleTask` queue: (id, code, repeat).
pub(crate) tasks: Vec<(u32, String, bool)>,
pub(crate) next_task: u32,
pub(crate) name: String,
/// The script context running (its `app.scheduleTask` tasks are kept under this id, see
/// [`crate::tasks`]).
pub(crate) host: u32,
/// Running on a script-host thread: modal dialogs hand the session back to the caller
/// through this link while they wait.
pub(crate) link: Option<crate::ui::Link>,
}
impl Active {
pub(crate) fn new(session: Session, name: &str) -> Active {
Active { session, output: vec![], groups: vec![], tasks: vec![], next_task: 0, name: name.to_string(), link: None }
pub(crate) fn new(session: Session, name: &str, host: u32) -> Active {
Active { session, output: vec![], groups: vec![], name: name.to_string(), host, link: None }
}
}
@@ -268,23 +268,29 @@ pub(crate) fn collapse(s: &mut Session, name: &str, snap: &Arc<Project>) {
h.merge_key = None;
}
/// `__task("schedule", code, repeat)` → id; `__task("cancel", id)`.
/// `__task("schedule", code, delayMs, repeat)` → id; `__task("cancel", id)` (see
/// [`crate::tasks`]).
fn native_task(_: &JsValue, args: &[JsValue], ctx: &mut Context) -> JsResult<JsValue> {
let op = arg_string(args, 0, ctx)?;
if op == "cancel" {
let id = args.get(1).cloned().unwrap_or_default().to_number(ctx)? as u32;
let _ = with_active(|a| {
a.tasks.retain(|t| t.0 != id);
Ok(())
});
let id = args.get(1).cloned().unwrap_or_default().to_number(ctx)?;
if id.is_finite() && id >= 0.0 && id <= f64::from(u32::MAX) {
let _ = with_active(|a| {
crate::tasks::cancel(a.host, id as u32);
Ok(())
});
}
return Ok(JsValue::undefined());
}
let code = arg_string(args, 1, ctx)?;
let repeat = args.get(2).cloned().unwrap_or_default().to_boolean();
let delay = args.get(2).cloned().unwrap_or_default().to_number(ctx)?;
let repeat = args.get(3).cloned().unwrap_or_default().to_boolean();
let now = web_time::Instant::now();
let id = with_active(|a| {
a.next_task += 1;
let id = a.next_task;
a.tasks.push((id, code, repeat));
let id = crate::tasks::schedule(a.host, &a.name, a.session.script_ui.project_epoch, code, delay, repeat, now)?;
if !a.session.script_ui.task_hosts.contains(&a.host) {
a.session.script_ui.task_hosts.push(a.host);
}
Ok(id)
})
.map_err(throw)?;
@@ -415,7 +421,7 @@ pub(crate) fn new_context() -> Result<Context, String> {
(js_string!("__query"), 2, native_query),
(js_string!("__print"), 1, native_print),
(js_string!("__undo"), 2, native_undo),
(js_string!("__task"), 3, native_task),
(js_string!("__task"), 4, native_task),
(js_string!("__file"), 3, native_file),
(js_string!("__sock"), 3, crate::socket::native_sock),
(js_string!("__uiNewId"), 0, crate::ui::native_new_id),
@@ -553,14 +559,11 @@ pub(crate) fn settle() -> Vec<String> {
.unwrap_or_default()
}
/// Evaluate a script file's code, then the one-shot tasks it scheduled (`app.scheduleTask`).
pub(crate) fn eval_with_tasks(ctx: &mut Context, code: &str, file: &str) -> Result<J, ScriptError> {
/// Evaluate a script file's code, then the one-shot tasks it scheduled (`app.scheduleTask`;
/// repeating ones run later, see [`crate::tasks`]).
pub(crate) fn eval_with_tasks(ctx: &mut Context, code: &str, file: &str, host: u32) -> Result<J, ScriptError> {
let mut r = eval_in(ctx, code, file);
let tasks = with_active(|a| Ok(std::mem::take(&mut a.tasks))).unwrap_or_default();
for (_, code, repeat) in tasks {
if repeat {
continue;
}
for code in crate::tasks::take_one_shots(host) {
if let Err(e) = eval_in(ctx, &code, "scheduled task")
&& r.is_ok()
{
@@ -676,7 +679,7 @@ pub fn check_nesting(code: &str) -> Result<(), String> {
pub(crate) fn run_inline(session: &mut Session, req: &ScriptRequest) -> Outcome {
let host = if req.console { crate::ui::CONSOLE_HOST } else { crate::ui::new_host_id() };
let s = std::mem::take(session);
ACTIVE.with(|a| *a.borrow_mut() = Some(Active::new(s, req.name)));
ACTIVE.with(|a| *a.borrow_mut() = Some(Active::new(s, req.name, host)));
let guard = Restore { target: session };
let file = if req.name.is_empty() { "script" } else { req.name };
let (result, windows) = if req.console {
@@ -697,10 +700,11 @@ pub(crate) fn run_inline(session: &mut Session, req: &ScriptRequest) -> Outcome
} else {
match new_context() {
Ok(mut ctx) => {
let r = eval_with_tasks(&mut ctx, req.code, file);
let r = eval_with_tasks(&mut ctx, req.code, file, host);
let windows = crate::ui::snapshot(&mut ctx);
if windows.iter().any(|w| w.visible) {
// Palettes and panels outlive the run: keep their context for the handlers.
if windows.iter().any(|w| w.visible) || crate::tasks::has(host) {
// Palettes, panels and repeating tasks outlive the run: keep their context
// for the handlers and tasks.
crate::ui::keep_inline(host, ctx);
}
(r, windows)
@@ -710,6 +714,7 @@ pub(crate) fn run_inline(session: &mut Session, req: &ScriptRequest) -> Outcome
};
let output = settle();
drop(guard);
crate::tasks::set_epoch(host, session.script_ui.project_epoch);
crate::ui::publish(session, host, req.name, windows);
outcome(output, result)
}