diff --git a/crates/egui_inspection/src/plugin.rs b/crates/egui_inspection/src/plugin.rs index 4784bd3b7..0ae857ab1 100644 --- a/crates/egui_inspection/src/plugin.rs +++ b/crates/egui_inspection/src/plugin.rs @@ -56,6 +56,9 @@ enum Phase { /// A screenshot was dispatched with this `user_data` id; reply when the matching /// [`egui::Event::Screenshot`] arrives. AwaitScreenshot { id: u64 }, + + /// Watching for the app to go idle. + Settle { steps_taken: u64, max_steps: u64 }, } struct InFlight { @@ -116,7 +119,12 @@ impl InspectionPlugin { /// While requests are still in flight, keep the UI loop spinning — reactive apps would /// otherwise go idle between hooks before a screenshot round-trips. fn maybe_repaint(&self, ctx: &Context) { - if !self.in_flight.is_empty() { + // Don't repaint if there's only a `Request::Settle`. + if self + .in_flight + .iter() + .any(|item| !matches!(item.req, Request::Settle { .. })) + { ctx.request_repaint(); } } @@ -236,6 +244,13 @@ impl egui::Plugin for InspectionPlugin { item.phase = Phase::AwaitScreenshot { id }; true } + Request::Settle { max_steps } => { + item.phase = Phase::Settle { + steps_taken: 0, + max_steps: *max_steps, + }; + true + } } }); self.next_screenshot_id = next_id; @@ -249,9 +264,14 @@ impl egui::Plugin for InspectionPlugin { return; } + let immediate_repaint = output + .viewport_output + .values() + .any(|viewport| viewport.repaint_delay == Duration::ZERO); + let step = self.step; self.in_flight - .retain_mut(|item| match (&item.phase, &item.req) { + .retain_mut(|item| match (&mut item.phase, &item.req) { (Phase::AwaitOutput, Request::GetTree) => { if let Some(reply) = item.reply.take() { reply(Response::Tree { @@ -268,6 +288,27 @@ impl egui::Plugin for InspectionPlugin { } false } + ( + Phase::Settle { + steps_taken, + max_steps, + }, + Request::Settle { .. }, + ) => { + *steps_taken += 1; + let steps_exceeded = *steps_taken >= *max_steps; + if !immediate_repaint || steps_exceeded { + if let Some(reply) = item.reply.take() { + reply(Response::Settled { + settled: !immediate_repaint, + steps: *steps_taken, + }); + } + false + } else { + true + } + } _ => true, }); diff --git a/crates/egui_inspection/src/protocol.rs b/crates/egui_inspection/src/protocol.rs index 12662ab19..631e7c7b1 100644 --- a/crates/egui_inspection/src/protocol.rs +++ b/crates/egui_inspection/src/protocol.rs @@ -63,6 +63,11 @@ pub enum Request { /// (via [`egui::ViewportCommand::InnerSize`]). Reply: [`Response::Done`]. This is the one /// action that isn't expressible as an [`egui::Event`]. Resize { width: u32, height: u32 }, + + /// Wait until the app goes idle, then reply [`Response::Settled`]. + /// + /// Will wait for at most `max_steps`. + Settle { max_steps: u64 }, } /// Sent peer → inspector, exactly one per [`Request`]. @@ -99,6 +104,15 @@ pub enum Response { /// (not merely received): the events were processed by a frame, or the resize dispatched. Done, + /// Reply to [`Request::Settle`]. + Settled { + /// Did we settle within `max_steps`? + settled: bool, + + /// How many frames did we run until we settled? + steps: u64, + }, + /// The peer failed to service the request (recoverable; the connection stays open). Error { message: String }, }