diff --git a/crates/ui-egui/src/scripts_menu.rs b/crates/ui-egui/src/scripts_menu.rs index bac2d1121..58be7b7b8 100644 --- a/crates/ui-egui/src/scripts_menu.rs +++ b/crates/ui-egui/src/scripts_menu.rs @@ -38,6 +38,8 @@ struct State { revision: u64, /// The listing being read on another thread. reading: Option>, + /// Another read was asked for while one was under way; [`poll`] starts it when that one ends. + pending: bool, /// A first read was asked for. started: bool, /// The window was the active one at the last [`poll`]. @@ -94,12 +96,14 @@ pub fn revision() -> u64 { state().revision } -/// Read the Scripts folder again on another thread, unless a read is under way or the session has -/// no folder (the web, and a run without preferences). `ctx` is asked to draw when the read ends. +/// Read the Scripts folder again on another thread, unless the session has no folder (the web, and +/// a run without preferences). While a read is under way, another read starts when it ends +/// ([`poll`]). `ctx` is asked to draw when the read ends. pub fn refresh(app: &VectorcraftApp, ctx: Option<&egui::Context>) { let Some(root) = app.session.scripts_dir().map(Path::to_path_buf) else { return }; let mut state = state(); if state.reading.is_some() { + state.pending = true; return; } let (tx, rx) = std::sync::mpsc::channel(); @@ -119,7 +123,7 @@ pub fn refresh(app: &VectorcraftApp, ctx: Option<&egui::Context>) { } /// Once per iteration: start a read on the first one and when the window becomes the active one, -/// and take in a read that ended. +/// take in a read that ended, and then start a read asked for while it was under way. pub fn poll(app: &VectorcraftApp, ctx: &egui::Context) { let focused = ctx.input(|i| i.focused); let start = { @@ -132,6 +136,17 @@ pub fn poll(app: &VectorcraftApp, ctx: &egui::Context) { refresh(app, Some(ctx)); } take(false); + let again = { + let mut state = state(); + let again = state.pending && state.reading.is_none(); + if again { + state.pending = false; + } + again + }; + if again { + refresh(app, Some(ctx)); + } } /// Take in the read under way: when it has ended, or with `wait` once it ends. @@ -158,6 +173,19 @@ pub(crate) fn wait() { take(true); } +/// Wait for the read under way to end and leave its listing to be taken in, so the read is under +/// way until the next [`poll`]. +#[cfg(test)] +pub(crate) fn end_untaken() { + let mut state = state(); + let Some(reading) = &state.reading else { return }; + let (tx, rx) = std::sync::mpsc::channel(); + if let Ok(listing) = reading.recv() { + let _ = tx.send(listing); + } + state.reading = Some(rx); +} + /// File › Scripts' items: the listed scripts and subfolders, a note when some may be missing or /// the folder cannot be read, then Check Script… and Other Script…. pub fn items() -> Vec { diff --git a/crates/ui-egui/src/tests/scripts_menu.rs b/crates/ui-egui/src/tests/scripts_menu.rs index 1cfb46287..f18b60f63 100644 --- a/crates/ui-egui/src/tests/scripts_menu.rs +++ b/crates/ui-egui/src/tests/scripts_menu.rs @@ -317,6 +317,29 @@ fn the_folder_is_read_at_the_first_frame_and_when_the_window_becomes_active() { let _ = std::fs::remove_dir_all(dir); } +#[test] +fn a_read_asked_for_during_another_runs_when_that_one_ends() { + let dir = folder("pending"); + let app = app(&dir); + write(&dir, "first.js", ""); + let ctx = egui::Context::default(); + let frame = || { + let mut out = ctx.run_ui(egui::RawInput { focused: true, ..Default::default() }, |ui| scripts_menu::poll(&app, ui.ctx())); + out.textures_delta.clear(); + }; + frame(); + scripts_menu::wait(); + // A read that has passed the folder when a script is added, and the File menu opening then. + scripts_menu::refresh(&app, None); + scripts_menu::end_untaken(); + write(&dir, "added.js", ""); + scripts_menu::refresh(&app, None); + frame(); + scripts_menu::wait(); + assert!(shape(&scripts()).starts_with("added, first, -"), "{}", shape(&scripts())); + let _ = std::fs::remove_dir_all(dir); +} + #[test] fn an_app_without_a_scripts_folder_keeps_the_two_fixed_items() { let mut app = VectorcraftApp::new(Session::new(), crate::Services::default());