mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 04:40:03 -04:00
More cleanup
This commit is contained in:
@@ -79,7 +79,7 @@ pub struct Harness<'a, State: 'static = ()> {
|
|||||||
output: egui::FullOutput,
|
output: egui::FullOutput,
|
||||||
app: AppKind<'a, State>,
|
app: AppKind<'a, State>,
|
||||||
response: Option<egui::Response>,
|
response: Option<egui::Response>,
|
||||||
state: State,
|
state: Option<State>,
|
||||||
renderer: Box<dyn TestRenderer>,
|
renderer: Box<dyn TestRenderer>,
|
||||||
max_steps: u64,
|
max_steps: u64,
|
||||||
step_dt: f32,
|
step_dt: f32,
|
||||||
@@ -170,7 +170,7 @@ impl<'a, State: 'static> Harness<'a, State> {
|
|||||||
kittest: kittest::State::new(initial_accesskit),
|
kittest: kittest::State::new(initial_accesskit),
|
||||||
output,
|
output,
|
||||||
response,
|
response,
|
||||||
state,
|
state: Some(state),
|
||||||
renderer,
|
renderer,
|
||||||
max_steps,
|
max_steps,
|
||||||
step_dt,
|
step_dt,
|
||||||
@@ -208,41 +208,12 @@ impl<'a, State: 'static> Harness<'a, State> {
|
|||||||
self.plugins.push(Box::new(plugin));
|
self.plugins.push(Box::new(plugin));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Borrow a registered plugin by type. Returns the first plugin of the matching type
|
|
||||||
/// in registration order, or `None` if no plugin of that type is registered.
|
|
||||||
pub fn plugin<P: Plugin<State>>(&self) -> Option<&P> {
|
|
||||||
self.plugins
|
|
||||||
.iter()
|
|
||||||
.find_map(|p| (&**p as &dyn std::any::Any).downcast_ref::<P>())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Mutably borrow a registered plugin by type.
|
|
||||||
pub fn plugin_mut<P: Plugin<State>>(&mut self) -> Option<&mut P> {
|
|
||||||
self.plugins
|
|
||||||
.iter_mut()
|
|
||||||
.find_map(|p| (&mut **p as &mut dyn std::any::Any).downcast_mut::<P>())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remove and return the first plugin of the given type.
|
|
||||||
pub fn take_plugin<P: Plugin<State>>(&mut self) -> Option<Box<P>> {
|
|
||||||
let idx = self
|
|
||||||
.plugins
|
|
||||||
.iter()
|
|
||||||
.position(|p| (&**p as &dyn std::any::Any).is::<P>())?;
|
|
||||||
let boxed = self.plugins.remove(idx);
|
|
||||||
let raw: *mut dyn Plugin<State> = Box::into_raw(boxed);
|
|
||||||
// SAFETY: `is::<P>()` confirmed the concrete type is `P`. Fat-to-thin pointer
|
|
||||||
// cast preserves the data pointer, which is the address of the underlying `P`.
|
|
||||||
#[expect(unsafe_code)]
|
|
||||||
Some(unsafe { Box::from_raw(raw.cast::<P>()) })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Advance the harness by one frame without firing plugin hooks.
|
/// 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
|
/// This is useful for running steps within a plugin, without ending in an infinite loop where
|
||||||
/// the plugin is called again.
|
/// the plugin is called again.
|
||||||
pub fn step_no_side_effects(&mut self) {
|
pub fn step_no_side_effects(&mut self) {
|
||||||
self._step_inner(false);
|
self._step_no_side_effects(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [`std::panic::Location`] of the most recent public `#[track_caller]` entry point
|
/// [`std::panic::Location`] of the most recent public `#[track_caller]` entry point
|
||||||
@@ -256,7 +227,7 @@ impl<'a, State: 'static> Harness<'a, State> {
|
|||||||
&self.consumed_event_locations
|
&self.consumed_event_locations
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dispatch(&mut self, mut f: impl FnMut(&mut dyn Plugin<State>, &mut Self)) {
|
fn plugin_dispatch(&mut self, mut f: impl FnMut(&mut dyn Plugin<State>, &mut Self)) {
|
||||||
if self.plugins.is_empty() {
|
if self.plugins.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -339,7 +310,7 @@ impl<'a, State: 'static> Harness<'a, State> {
|
|||||||
EventType::Event(event, loc) => {
|
EventType::Event(event, loc) => {
|
||||||
self.consumed_event_locations.push(loc);
|
self.consumed_event_locations.push(loc);
|
||||||
self.input.events.push(event.clone());
|
self.input.events.push(event.clone());
|
||||||
self.dispatch(|p, h| p.on_event(h, &event));
|
self.plugin_dispatch(|p, h| p.on_event(h, &event));
|
||||||
}
|
}
|
||||||
EventType::Modifiers(modifiers, loc) => {
|
EventType::Modifiers(modifiers, loc) => {
|
||||||
self.consumed_event_locations.push(loc);
|
self.consumed_event_locations.push(loc);
|
||||||
@@ -352,25 +323,25 @@ impl<'a, State: 'static> Harness<'a, State> {
|
|||||||
|
|
||||||
/// Run a single step, firing `before_step` / `after_step` plugin hooks.
|
/// Run a single step, firing `before_step` / `after_step` plugin hooks.
|
||||||
fn _step(&mut self, sizing_pass: bool) {
|
fn _step(&mut self, sizing_pass: bool) {
|
||||||
self.dispatch(|p, h| p.before_step(h));
|
self.plugin_dispatch(|p, h| p.before_step(h));
|
||||||
self._step_inner(sizing_pass);
|
self._step_no_side_effects(sizing_pass);
|
||||||
self.dispatch(|p, h| p.after_step(h));
|
self.plugin_dispatch(|p, h| p.after_step(h));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Core frame advance. Does NOT fire plugin hooks — callable from within
|
/// Core frame advance. Does NOT fire plugin hooks — callable from within
|
||||||
/// hooks via [`Self::step_no_side_effects`] without recursing.
|
/// hooks via [`Self::step_no_side_effects`] without recursing.
|
||||||
fn _step_inner(&mut self, sizing_pass: bool) {
|
fn _step_no_side_effects(&mut self, sizing_pass: bool) {
|
||||||
self.input.predicted_dt = self.step_dt;
|
self.input.predicted_dt = self.step_dt;
|
||||||
|
|
||||||
let mut output = self.ctx.run_ui(self.input.take(), |ui| {
|
let mut output = self.ctx.run_ui(self.input.take(), |ui| {
|
||||||
self.response = self.app.run(ui, &mut self.state, sizing_pass);
|
self.response = self.app.run(ui, self.state.as_mut().unwrap(), sizing_pass);
|
||||||
});
|
});
|
||||||
let accesskit_update = output
|
let accesskit_update = output
|
||||||
.platform_output
|
.platform_output
|
||||||
.accesskit_update
|
.accesskit_update
|
||||||
.take()
|
.take()
|
||||||
.expect("AccessKit was disabled");
|
.expect("AccessKit was disabled");
|
||||||
self.dispatch(|p, h| p.on_accesskit_update(h, &accesskit_update));
|
self.plugin_dispatch(|p, h| p.on_accesskit_update(h, &accesskit_update));
|
||||||
self.kittest.update(accesskit_update);
|
self.kittest.update(accesskit_update);
|
||||||
self.renderer.handle_delta(&output.textures_delta);
|
self.renderer.handle_delta(&output.textures_delta);
|
||||||
self.output = output;
|
self.output = output;
|
||||||
@@ -442,7 +413,7 @@ impl<'a, State: 'static> Harness<'a, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn _try_run(&mut self, sleep: bool) -> Result<u64, ExceededMaxStepsError> {
|
fn _try_run(&mut self, sleep: bool) -> Result<u64, ExceededMaxStepsError> {
|
||||||
self.dispatch(|p, h| p.before_run(h));
|
self.plugin_dispatch(|p, h| p.before_run(h));
|
||||||
|
|
||||||
let mut steps = 0;
|
let mut steps = 0;
|
||||||
let result = loop {
|
let result = loop {
|
||||||
@@ -464,7 +435,7 @@ impl<'a, State: 'static> Harness<'a, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.dispatch(|p, h| p.after_run(h, result.as_ref().map(|s| *s)));
|
self.plugin_dispatch(|p, h| p.after_run(h, result.as_ref().map(|s| *s)));
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -566,12 +537,17 @@ impl<'a, State: 'static> Harness<'a, State> {
|
|||||||
|
|
||||||
/// Access the state.
|
/// Access the state.
|
||||||
pub fn state(&self) -> &State {
|
pub fn state(&self) -> &State {
|
||||||
&self.state
|
self.state.as_ref().expect("state already taken via into_state")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Access the state mutably.
|
/// Access the state mutably.
|
||||||
pub fn state_mut(&mut self) -> &mut State {
|
pub fn state_mut(&mut self) -> &mut State {
|
||||||
&mut self.state
|
self.state.as_mut().expect("state already taken via into_state")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consume the harness and return the state.
|
||||||
|
pub fn into_state(mut self) -> State {
|
||||||
|
self.state.take().expect("state already taken via into_state")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queue an event to be processed in the next frame.
|
/// Queue an event to be processed in the next frame.
|
||||||
@@ -797,7 +773,7 @@ impl<'a, State: 'static> Harness<'a, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let image = self.renderer.render(&self.ctx, &output)?;
|
let image = self.renderer.render(&self.ctx, &output)?;
|
||||||
self.dispatch(|p, h| p.on_render(h, &image));
|
self.plugin_dispatch(|p, h| p.on_render(h, &image));
|
||||||
Ok(image)
|
Ok(image)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -890,7 +866,7 @@ impl<'a, State: 'static> Harness<'a, State> {
|
|||||||
if let AppKind::Eframe(crate::app_kind::AppKindEframe { get_app, .. }) =
|
if let AppKind::Eframe(crate::app_kind::AppKindEframe { get_app, .. }) =
|
||||||
&mut self.harness.app
|
&mut self.harness.app
|
||||||
{
|
{
|
||||||
get_app(&mut self.harness.state).logic(ctx, frame);
|
get_app(self.harness.state.as_mut().unwrap()).logic(ctx, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -898,9 +874,9 @@ impl<'a, State: 'static> Harness<'a, State> {
|
|||||||
let harness = &mut self.harness;
|
let harness = &mut self.harness;
|
||||||
match &mut harness.app {
|
match &mut harness.app {
|
||||||
AppKind::Ui(f) => f(ui),
|
AppKind::Ui(f) => f(ui),
|
||||||
AppKind::UiState(f) => f(ui, &mut harness.state),
|
AppKind::UiState(f) => f(ui, harness.state.as_mut().unwrap()),
|
||||||
AppKind::Eframe(crate::app_kind::AppKindEframe { get_app, .. }) => {
|
AppKind::Eframe(crate::app_kind::AppKindEframe { get_app, .. }) => {
|
||||||
get_app(&mut harness.state).ui(ui, frame);
|
get_app(harness.state.as_mut().unwrap()).ui(ui, frame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -967,10 +943,10 @@ impl<State: 'static> Drop for Harness<'_, State> {
|
|||||||
|
|
||||||
if std::thread::panicking() {
|
if std::thread::panicking() {
|
||||||
plugin::with_fail_test_result(|result| {
|
plugin::with_fail_test_result(|result| {
|
||||||
self.dispatch(|p, h| p.on_test_result(h, fail_ref(&result)));
|
self.plugin_dispatch(|p, h| p.on_test_result(h, fail_ref(&result)));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
self.dispatch(|p, h| p.on_test_result(h, TestResult::Pass));
|
self.plugin_dispatch(|p, h| p.on_test_result(h, TestResult::Pass));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
//! renders, snapshots, and final pass/fail. Register plugins via
|
//! renders, snapshots, and final pass/fail. Register plugins via
|
||||||
//! [`crate::HarnessBuilder::with_plugin`] or [`crate::Harness::add_plugin`].
|
//! [`crate::HarnessBuilder::with_plugin`] or [`crate::Harness::add_plugin`].
|
||||||
|
|
||||||
use std::any::Any;
|
|
||||||
|
|
||||||
use crate::{ExceededMaxStepsError, Harness};
|
use crate::{ExceededMaxStepsError, Harness};
|
||||||
|
|
||||||
/// A plugin observes the test-harness lifecycle and can drive additional frames.
|
/// A plugin observes the test-harness lifecycle and can drive additional frames.
|
||||||
@@ -25,12 +23,6 @@ use crate::{ExceededMaxStepsError, Harness};
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Downcasting
|
|
||||||
///
|
|
||||||
/// [`Any`] is a supertrait, so [`Harness::plugin`] / [`Harness::plugin_mut`] /
|
|
||||||
/// [`Harness::take_plugin`] downcast registered plugins back to their concrete type via
|
|
||||||
/// trait upcasting. No boilerplate needed on your end.
|
|
||||||
///
|
|
||||||
/// # Re-entrancy
|
/// # Re-entrancy
|
||||||
///
|
///
|
||||||
/// Plugin hooks receive `&mut Harness`. Calling [`Harness::step`] / [`Harness::run`] /
|
/// Plugin hooks receive `&mut Harness`. Calling [`Harness::step`] / [`Harness::run`] /
|
||||||
@@ -38,7 +30,7 @@ use crate::{ExceededMaxStepsError, Harness};
|
|||||||
/// a plugin needs to advance the harness from inside a hook — e.g. an inspector that
|
/// a plugin needs to advance the harness from inside a hook — e.g. an inspector that
|
||||||
/// blocks on user input — use [`Harness::step_no_side_effects`] instead.
|
/// blocks on user input — use [`Harness::step_no_side_effects`] instead.
|
||||||
#[expect(unused_variables, reason = "default no-op impls")]
|
#[expect(unused_variables, reason = "default no-op impls")]
|
||||||
pub trait Plugin<State = ()>: Send + Any {
|
pub trait Plugin<State = ()>: Send + 'static {
|
||||||
/// Called once at the start of every `run()` / `try_run()` / `try_run_realtime()` /
|
/// Called once at the start of every `run()` / `try_run()` / `try_run_realtime()` /
|
||||||
/// `run_ok()` invocation, before the first step.
|
/// `run_ok()` invocation, before the first step.
|
||||||
fn before_run(&mut self, harness: &mut Harness<'_, State>) {}
|
fn before_run(&mut self, harness: &mut Harness<'_, State>) {}
|
||||||
|
|||||||
@@ -628,7 +628,7 @@ impl<State: 'static> Harness<'_, State> {
|
|||||||
Err(err) => return Err(SnapshotError::RenderError { err }),
|
Err(err) => return Err(SnapshotError::RenderError { err }),
|
||||||
};
|
};
|
||||||
let result = try_image_snapshot_options(&image, name.clone(), options);
|
let result = try_image_snapshot_options(&image, name.clone(), options);
|
||||||
self.dispatch(|p, h| p.on_snapshot(h, &name, &image, &result));
|
self.plugin_dispatch(|p, h| p.on_snapshot(h, &name, &image, &result));
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -196,22 +196,6 @@ fn mid_dispatch_registration_is_deferred() {
|
|||||||
// any "before_step" entries. Since we merged logs, we can't easily isolate — instead,
|
// any "before_step" entries. Since we merged logs, we can't easily isolate — instead,
|
||||||
// verify the harness does not deadlock / recurse.
|
// verify the harness does not deadlock / recurse.
|
||||||
harness.step();
|
harness.step();
|
||||||
assert!(harness.plugin::<CountingPlugin>().is_some());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Downcasting via `plugin::<P>()` / `plugin_mut::<P>()` / `take_plugin::<P>()`.
|
|
||||||
#[test]
|
|
||||||
fn downcast_plugin_by_type() {
|
|
||||||
let (plugin, _log) = CountingPlugin::new();
|
|
||||||
let mut harness = Harness::builder().with_plugin(plugin).build_ui(|ui| {
|
|
||||||
ui.label("hi");
|
|
||||||
});
|
|
||||||
|
|
||||||
assert!(harness.plugin::<CountingPlugin>().is_some());
|
|
||||||
assert!(harness.plugin_mut::<CountingPlugin>().is_some());
|
|
||||||
let taken = harness.take_plugin::<CountingPlugin>();
|
|
||||||
assert!(taken.is_some());
|
|
||||||
assert!(harness.plugin::<CountingPlugin>().is_none());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// When `Harness::drop` fires while a panic is unwinding, `on_test_result` gets `Fail`.
|
/// When `Harness::drop` fires while a panic is unwinding, `on_test_result` gets `Fail`.
|
||||||
|
|||||||
Reference in New Issue
Block a user