1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-26 22:53:14 -04:00

Pass accesskit tree to after_step, drop on_accesskit_update

Replaces the separate on_accesskit_update hook with a `&TreeUpdate`
parameter on `after_step` — one hook per step, tree delivered inline.
`step_no_side_effects` now returns the TreeUpdate so plugins driving
the harness from within their own hook (where nested dispatches are
suppressed) can still see it.

Also adds `#[track_caller]` to the internal `_step` / `_step_no_side_effects`
/ `_try_run` so `Location::caller()` inside `step()` walks up to the
user's original call site when reached via `run()`.
This commit is contained in:
lucasmerlin
2026-04-24 14:22:06 +02:00
parent c6490fcaf7
commit c71e6e2c6a
3 changed files with 30 additions and 21 deletions

View File

@@ -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<u64, ExceededMaxStepsError> {
self.plugin_dispatch(|p, h| p.before_run(h));

View File

@@ -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<S> Plugin<S> 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<State = ()>: 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,
) {
}

View File

@@ -41,7 +41,11 @@ impl<S> Plugin<S> 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<S: 'static> Plugin<S> 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<S: 'static> Plugin<S> 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;