diff --git a/crates/egui_kittest/src/lib.rs b/crates/egui_kittest/src/lib.rs index a6c69ce73..1a5fafcd8 100644 --- a/crates/egui_kittest/src/lib.rs +++ b/crates/egui_kittest/src/lib.rs @@ -210,10 +210,13 @@ impl<'a, State: 'static> Harness<'a, State> { /// Advance the harness by one frame without firing plugin hooks. /// - /// This is useful for running steps within a plugin, without ending in an infinite loop where - /// the plugin is called again. - pub fn step_no_side_effects(&mut self) { - self._step_no_side_effects(false); + /// Returns the AccessKit tree update produced by the frame. Useful for plugins driving + /// the harness from inside a hook: `after_step` normally delivers the tree, but nested + /// hook dispatches are suppressed, so plugins that call this from within their own + /// `after_step` need the return value to see the fresh tree. + #[track_caller] + pub fn step_no_side_effects(&mut self) -> egui::accesskit::TreeUpdate { + self._step_no_side_effects(false) } /// [`std::panic::Location`] of the most recent public `#[track_caller]` entry point @@ -322,15 +325,17 @@ impl<'a, State: 'static> Harness<'a, State> { } /// Run a single step, firing `before_step` / `after_step` plugin hooks. + #[track_caller] fn _step(&mut self, sizing_pass: bool) { self.plugin_dispatch(|p, h| p.before_step(h)); - self._step_no_side_effects(sizing_pass); - self.plugin_dispatch(|p, h| p.after_step(h)); + let accesskit_update = self._step_no_side_effects(sizing_pass); + self.plugin_dispatch(|p, h| p.after_step(h, &accesskit_update)); } /// Core frame advance. Does NOT fire plugin hooks — callable from within /// hooks via [`Self::step_no_side_effects`] without recursing. - fn _step_no_side_effects(&mut self, sizing_pass: bool) { + #[track_caller] + fn _step_no_side_effects(&mut self, sizing_pass: bool) -> egui::accesskit::TreeUpdate { self.input.predicted_dt = self.step_dt; let mut output = self.ctx.run_ui(self.input.take(), |ui| { @@ -341,10 +346,10 @@ impl<'a, State: 'static> Harness<'a, State> { .accesskit_update .take() .expect("AccessKit was disabled"); - self.plugin_dispatch(|p, h| p.on_accesskit_update(h, &accesskit_update)); - self.kittest.update(accesskit_update); + self.kittest.update(accesskit_update.clone()); self.renderer.handle_delta(&output.textures_delta); self.output = output; + accesskit_update } /// Calculate the rect that includes all popups and tooltips. @@ -408,6 +413,7 @@ impl<'a, State: 'static> Harness<'a, State> { } } + #[track_caller] fn _try_run(&mut self, sleep: bool) -> Result { self.plugin_dispatch(|p, h| p.before_run(h)); diff --git a/crates/egui_kittest/src/plugin.rs b/crates/egui_kittest/src/plugin.rs index 65e6b2e9d..a41bedb7f 100644 --- a/crates/egui_kittest/src/plugin.rs +++ b/crates/egui_kittest/src/plugin.rs @@ -13,11 +13,12 @@ use crate::{ExceededMaxStepsError, Harness}; /// State-agnostic plugins should impl for all `State` so they're reusable across harnesses: /// ``` /// use egui_kittest::{Harness, Plugin}; +/// use egui::accesskit::TreeUpdate; /// /// struct MyPlugin; /// /// impl Plugin for MyPlugin { -/// fn after_step(&mut self, _harness: &mut Harness<'_, S>) { +/// fn after_step(&mut self, _harness: &mut Harness<'_, S>, _tree: &TreeUpdate) { /// // ... /// } /// } @@ -48,17 +49,15 @@ pub trait Plugin: Send + 'static { fn before_step(&mut self, harness: &mut Harness<'_, State>) {} /// Called immediately after each single-frame step. - fn after_step(&mut self, harness: &mut Harness<'_, State>) {} - - /// Called after each single-frame step with the AccessKit tree update egui produced - /// for that frame, before it's applied to the internal kittest state. /// - /// Plugins that need the tree (e.g. to stream it to an external debugger) should - /// clone it here — the harness no longer retains it after this hook returns. - fn on_accesskit_update( + /// `accesskit_update` is the AccessKit tree update egui produced for the frame that + /// just ran. Plugins that need to retain it (e.g. to stream it to an external + /// debugger) should clone it here — the harness doesn't hold on to it after this hook + /// returns. + fn after_step( &mut self, harness: &mut Harness<'_, State>, - tree: &egui::accesskit::TreeUpdate, + accesskit_update: &egui::accesskit::TreeUpdate, ) { } diff --git a/crates/egui_kittest/tests/plugin.rs b/crates/egui_kittest/tests/plugin.rs index ef83d3ddc..30e0a4f7c 100644 --- a/crates/egui_kittest/tests/plugin.rs +++ b/crates/egui_kittest/tests/plugin.rs @@ -41,7 +41,11 @@ impl Plugin for CountingPlugin { fn before_step(&mut self, _h: &mut Harness<'_, S>) { self.push("before_step"); } - fn after_step(&mut self, _h: &mut Harness<'_, S>) { + fn after_step( + &mut self, + _h: &mut Harness<'_, S>, + _tree: &egui::accesskit::TreeUpdate, + ) { self.push("after_step"); } fn on_event(&mut self, _h: &mut Harness<'_, S>, _event: &egui::Event) { @@ -127,7 +131,7 @@ fn step_no_side_effects_skips_hooks() { drove: bool, } impl Plugin for DrivingPlugin { - fn after_step(&mut self, h: &mut Harness<'_, S>) { + fn after_step(&mut self, h: &mut Harness<'_, S>, _tree: &egui::accesskit::TreeUpdate) { self.log.lock().unwrap().push("after_step".into()); if !self.drove { self.drove = true; @@ -164,7 +168,7 @@ fn mid_dispatch_registration_is_deferred() { registered: bool, } impl Plugin for Registrar { - fn after_step(&mut self, h: &mut Harness<'_, S>) { + fn after_step(&mut self, h: &mut Harness<'_, S>, _tree: &egui::accesskit::TreeUpdate) { self.log.lock().unwrap().push("registrar:after_step".into()); if !self.registered { self.registered = true;