Skip to repository content782 lines · 26.8 KB · rust
tenant.openagents/omega
No repository description is available.
OpenAgents Git authority 2026-07-28T06:41:43.242Z Public web read
NIP-34 coordinate
30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omegaMaintainersHidden in public view
References2 branches · 1 tag
Read-only clone
git clone https://openagents.com/git/tenant.openagents/omega.gitBrowse files
bench_context.rs
1use std::{
2 cell::{OnceCell, RefCell},
3 future::Future,
4 rc::Rc,
5 sync::Arc,
6 time::Duration,
7};
8
9use anyhow::{Result, anyhow};
10use hdrhistogram::Histogram;
11
12use crate::{
13 AnyView, AnyWindowHandle, App, AppCell, AppContext, BackgroundExecutor, BenchDispatcher,
14 Bounds, Context, Empty, Entity, EntityId, Focusable, ForegroundExecutor, Global, Platform,
15 PlatformHeadlessRenderer, PlatformTextSystem, Render, Reservation, Task, TestPlatform,
16 VisualContext, Window, WindowBounds, WindowHandle, WindowOptions,
17 app::GpuiBorrow,
18 profiler::{self, FrameTiming, FrameTimingCollector},
19};
20
21/// Returns a benchmark platform backed by this thread's shared dispatcher.
22///
23/// The platform uses this thread's shared multithreaded [`BenchDispatcher`], so
24/// background work runs with production concurrency in real time. The dispatcher
25/// is cached per thread and reused across benchmark invocations so worker and
26/// timer threads persist for the whole process instead of being recreated for
27/// every Criterion calibration pass.
28///
29/// Text is shaped with the provided platform text system. Benchmarks generated
30/// by `#[gpui::bench]` use the current platform's text system, so text-heavy
31/// benchmark measurements include production shaping and glyph rasterization.
32///
33/// `headless_renderer_factory` supplies a renderer for benchmark windows, e.g.
34/// `gpui_platform::current_headless_renderer`. When present, scenes drawn by
35/// benchmarks are rasterized through the real sprite atlas and submitted to
36/// the GPU on present, so quad/sprite regressions show up in measurements.
37/// When `None`, presenting discards the scene. Currently only macOS provides
38/// a headless renderer (Metal), so GPU submission is excluded from benchmark
39/// measurements on other platforms.
40pub fn bench_platform(
41 headless_renderer_factory: Option<Box<dyn Fn() -> Option<Box<dyn PlatformHeadlessRenderer>>>>,
42 text_system: Arc<dyn PlatformTextSystem>,
43) -> Rc<dyn Platform> {
44 thread_local! {
45 static DISPATCHER: OnceCell<Arc<BenchDispatcher>> = const { OnceCell::new() };
46 }
47 let dispatcher = DISPATCHER.with(|cell| {
48 cell.get_or_init(|| Arc::new(BenchDispatcher::new()))
49 .clone()
50 });
51 let background_executor = BackgroundExecutor::new(dispatcher.clone());
52 let foreground_executor = ForegroundExecutor::new(dispatcher);
53 TestPlatform::with_platform(
54 background_executor,
55 foreground_executor,
56 text_system,
57 headless_renderer_factory,
58 ) as Rc<dyn Platform>
59}
60
61/// Default target frame rate when a benchmark doesn't specify `fps = N`.
62const DEFAULT_FPS: u64 = 120;
63
64const NANOS_PER_SECOND: u128 = 1_000_000_000;
65
66/// A small report produced by GPUI benchmarks.
67#[derive(Clone)]
68pub struct BenchReport {
69 frame_snapshot: Rc<RefCell<WindowFrameSnapshot>>,
70 frame_budget_nanos: u128,
71}
72
73impl Default for BenchReport {
74 fn default() -> Self {
75 Self::with_fps(DEFAULT_FPS)
76 }
77}
78
79impl BenchReport {
80 /// Creates a report whose per-frame budget is one frame at `fps` when
81 /// counting frame budget overruns.
82 pub fn with_fps(fps: u64) -> Self {
83 assert!(fps > 0, "frame rate must be greater than zero");
84 Self::with_frame_budget_nanos(NANOS_PER_SECOND / fps as u128)
85 }
86
87 /// Creates a report that treats `frame_budget_nanos` as the per-frame budget
88 /// when counting frame budget overruns.
89 pub fn with_frame_budget_nanos(frame_budget_nanos: u128) -> Self {
90 Self {
91 frame_snapshot: Rc::new(RefCell::new(WindowFrameSnapshot::new())),
92 frame_budget_nanos,
93 }
94 }
95
96 fn record_frame_timings<'i>(&self, timings: impl IntoIterator<Item = &'i FrameTiming>) {
97 let mut snapshot = self.frame_snapshot.borrow_mut();
98 // `.ok()` on `record`: this operation is infallible (the histograms auto-resize).
99 for timing in timings {
100 snapshot
101 .draw
102 .record(timing.draw_duration().as_nanos() as u64)
103 .ok();
104 if let Some(dirty_to_draw) = timing.dirty_to_draw_duration() {
105 snapshot
106 .dirty_to_draw
107 .record(dirty_to_draw.as_nanos() as u64)
108 .ok();
109 }
110 if timing.invalidations > 0 {
111 snapshot
112 .invalidations_per_frame
113 .record(timing.invalidations)
114 .ok();
115 }
116 }
117 }
118
119 fn total_budget_overruns(&self, histogram: &Histogram<u64>) -> u64 {
120 histogram
121 .iter_recorded()
122 .map(|value| {
123 self.budget_overruns(Duration::from_nanos(value.value_iterated_to()))
124 * value.count_at_value()
125 })
126 .sum()
127 }
128
129 /// Returns how many whole frame budgets `foreground_time` exceeded the
130 /// per frame budget by. This is a synthetic proxy for missed frames: the
131 /// benchmark harness has no vsync, so it counts how many frame deadlines
132 /// would have elapsed while the foreground thread was busy.
133 fn budget_overruns(&self, foreground_time: Duration) -> u64 {
134 let foreground_nanos = foreground_time.as_nanos();
135 if foreground_nanos <= self.frame_budget_nanos {
136 return 0;
137 }
138
139 let over_budget_nanos = foreground_nanos - self.frame_budget_nanos;
140 over_budget_nanos.div_ceil(self.frame_budget_nanos) as u64
141 }
142
143 /// Prints this report to stderr.
144 pub fn print(&self, benchmark_name: Option<&'static str>) {
145 let frame_snapshot = self.frame_snapshot.borrow();
146 if frame_snapshot.is_empty() {
147 return;
148 }
149
150 let benchmark_name = benchmark_name.unwrap_or("unknown benchmark");
151 eprintln!("GPUI bench report (all observed iterations): {benchmark_name}");
152 eprintln!(" note: includes Criterion warmup/calibration");
153 self.print_histogram("window dirty-to-draw", &frame_snapshot.dirty_to_draw);
154 self.print_histogram("window draw", &frame_snapshot.draw);
155 if !frame_snapshot.invalidations_per_frame.is_empty() {
156 eprintln!(
157 " invalidations per frame: mean {:.2}, max {}",
158 frame_snapshot.invalidations_per_frame.mean(),
159 frame_snapshot.invalidations_per_frame.max()
160 );
161 }
162 }
163
164 fn print_histogram(&self, name: &str, histogram: &Histogram<u64>) {
165 if histogram.is_empty() {
166 return;
167 }
168
169 let max_foreground_time = Duration::from_nanos(histogram.max());
170 eprintln!(" {name}:");
171 eprintln!(" samples: {}", histogram.len());
172 eprintln!(
173 " mean: {}",
174 format_duration(Duration::from_nanos(histogram.mean() as u64))
175 );
176 eprintln!(
177 " p50: {}",
178 format_duration(Duration::from_nanos(histogram.value_at_quantile(0.50)))
179 );
180 eprintln!(
181 " p90: {}",
182 format_duration(Duration::from_nanos(histogram.value_at_quantile(0.90)))
183 );
184 eprintln!(
185 " p95: {}",
186 format_duration(Duration::from_nanos(histogram.value_at_quantile(0.95)))
187 );
188 eprintln!(
189 " p99: {}",
190 format_duration(Duration::from_nanos(histogram.value_at_quantile(0.99)))
191 );
192 eprintln!(" max: {}", format_duration(max_foreground_time));
193 eprintln!(
194 " frame budget overruns total: {}",
195 self.total_budget_overruns(histogram)
196 );
197 eprintln!(
198 " frame budget overruns max: {}",
199 self.budget_overruns(max_foreground_time)
200 );
201 }
202}
203
204struct WindowFrameSnapshot {
205 dirty_to_draw: Histogram<u64>,
206 draw: Histogram<u64>,
207 invalidations_per_frame: Histogram<u64>,
208}
209
210impl WindowFrameSnapshot {
211 fn new() -> Self {
212 Self {
213 dirty_to_draw: Histogram::new(3).expect("3 significant digits is valid"),
214 draw: Histogram::new(3).expect("3 significant digits is valid"),
215 invalidations_per_frame: Histogram::new(3).expect("3 significant digits is valid"),
216 }
217 }
218
219 fn is_empty(&self) -> bool {
220 self.dirty_to_draw.is_empty() && self.draw.is_empty()
221 }
222}
223
224fn format_duration(duration: Duration) -> String {
225 format!("{:.3}ms", duration.as_secs_f64() * 1000.)
226}
227
228/// Enables frame tracing for the duration of a measurement and collects the
229/// frames recorded within it. The previous tracing state is restored on drop,
230/// so a panicking measurement doesn't leave tracing enabled for unrelated code
231/// (e.g. a later benchmark in the same process).
232struct FrameTraceScope {
233 collector: FrameTimingCollector,
234 was_already_enabled: bool,
235}
236
237impl FrameTraceScope {
238 fn start() -> Self {
239 let was_already_enabled = !profiler::set_frame_trace_enabled(true);
240 Self {
241 collector: FrameTimingCollector::new(),
242 was_already_enabled,
243 }
244 }
245
246 fn finish(mut self) -> Vec<FrameTiming> {
247 self.collector.collect_unseen()
248 // Dropping `self` restores the previous tracing state.
249 }
250}
251
252impl Drop for FrameTraceScope {
253 fn drop(&mut self) {
254 if !self.was_already_enabled {
255 profiler::set_frame_trace_enabled(false);
256 }
257 }
258}
259
260/// A GPUI app context for Criterion benchmarks.
261///
262/// `BenchAppContext` is intentionally separate from `TestAppContext`: it owns a
263/// benchmark app instance and exposes only the app/window operations needed by
264/// benchmark setup. Criterion remains responsible for the measured loop via its
265/// `Bencher` API.
266#[derive(Clone)]
267pub struct BenchAppContext<'a, 'measurement> {
268 app: Rc<AppCell>,
269 background_executor: BackgroundExecutor,
270 foreground_executor: ForegroundExecutor,
271 benchmark_name: Option<&'static str>,
272 bencher: Rc<RefCell<Option<&'a mut criterion::Bencher<'measurement>>>>,
273 report: BenchReport,
274}
275
276impl<'a, 'measurement> BenchAppContext<'a, 'measurement> {
277 /// Creates a new benchmark app context backed by the provided platform.
278 ///
279 /// The platform's executors must be backed by a [`BenchDispatcher`]
280 /// (see [`bench_platform`]) so the context can drain foreground work via
281 /// [`Self::run_until_idle`]; panics otherwise.
282 pub fn new(
283 platform: Rc<dyn Platform>,
284 benchmark_name: Option<&'static str>,
285 bencher: &'a mut criterion::Bencher<'measurement>,
286 ) -> Self {
287 Self::build(platform, benchmark_name, bencher, BenchReport::default())
288 }
289
290 /// Creates a new benchmark app context backed by the provided platform.
291 ///
292 /// The platform's executors must be backed by a [`BenchDispatcher`]
293 /// (see [`bench_platform`]) so the context can drain foreground work via
294 /// [`Self::run_until_idle`]; panics otherwise.
295 #[doc(hidden)]
296 pub fn new_with_platform_and_report(
297 platform: Rc<dyn Platform>,
298 benchmark_name: Option<&'static str>,
299 bencher: &'a mut criterion::Bencher<'measurement>,
300 report: BenchReport,
301 ) -> Self {
302 Self::build(platform, benchmark_name, bencher, report)
303 }
304
305 fn build(
306 platform: Rc<dyn Platform>,
307 benchmark_name: Option<&'static str>,
308 bencher: &'a mut criterion::Bencher<'measurement>,
309 report: BenchReport,
310 ) -> Self {
311 let background_executor = platform.background_executor();
312 // Validate up front so misconfiguration fails at construction with a
313 // clear message instead of deep inside `run_until_idle`.
314 assert!(
315 background_executor.dispatcher().as_bench().is_some(),
316 "BenchAppContext requires a platform whose executors are backed by a \
317 BenchDispatcher; construct one with gpui::bench_platform"
318 );
319 let foreground_executor = platform.foreground_executor();
320 let asset_source = Arc::new(());
321 let http_client = http_client::FakeHttpClient::with_404_response();
322 let app = App::new_app(platform, asset_source, http_client);
323
324 Self {
325 app,
326 background_executor,
327 foreground_executor,
328 benchmark_name,
329 bencher: Rc::new(RefCell::new(Some(bencher))),
330 report,
331 }
332 }
333
334 /// The benchmark function name that created this context.
335 pub fn benchmark_name(&self) -> Option<&'static str> {
336 self.benchmark_name
337 }
338
339 /// Returns the background executor used by this benchmark app.
340 pub fn background_executor(&self) -> &BackgroundExecutor {
341 &self.background_executor
342 }
343
344 /// Returns the foreground executor used by this benchmark app.
345 pub fn foreground_executor(&self) -> &ForegroundExecutor {
346 &self.foreground_executor
347 }
348
349 /// Updates the app and flushes synchronous GPUI effects afterward.
350 pub fn update<R>(&mut self, update: impl FnOnce(&mut App) -> R) -> R {
351 let mut app = self.app.borrow_mut();
352 app.update(update)
353 }
354
355 /// Reads app state.
356 pub fn read<R>(&self, read: impl FnOnce(&App) -> R) -> R {
357 let app = self.app.borrow();
358 read(&app)
359 }
360
361 /// Runs queued foreground tasks on this thread and waits for in flight
362 /// background work to finish. Timers that aren't due yet are not waited
363 /// for (see [`BenchDispatcher::run_until_idle`]).
364 pub fn run_until_idle(&self) {
365 self.background_executor
366 .dispatcher()
367 .as_bench()
368 .expect("validated in BenchAppContext::build")
369 .run_until_idle();
370 }
371
372 /// Measures a generic benchmark workload using Criterion's iteration loop.
373 ///
374 /// The closure is invoked once per Criterion iteration with this
375 /// benchmark app context so it can update GPUI state.
376 ///
377 /// Any window draws triggered by the workload are recorded into the
378 /// benchmark's frame report through the GPUI frame profiler.
379 pub fn bench_iter(&mut self, mut benchmark: impl FnMut(&mut Self)) {
380 let bencher = self.take_bencher("bench_iter");
381 let collector = FrameTraceScope::start();
382 let mut benchmark = || benchmark(self);
383 bencher.iter(&mut benchmark);
384 self.report.record_frame_timings(collector.finish().iter());
385 self.replace_bencher(bencher);
386 }
387
388 /// Measures frame latency after updating a GPUI entity in its current window.
389 ///
390 /// Each iteration runs `update` against the entity in its current window. In
391 /// bench builds, flushing the update's effects synchronously draws dirty
392 /// windows. The entity should be part of the window's render tree, such as the
393 /// root view or a child of it.
394 ///
395 /// Frame timings are collected through the GPUI frame profiler
396 /// ([`crate::profiler::record_frame_timing`]), which is enabled for the
397 /// duration of the measurement.
398 pub fn bench_renderer<V>(
399 &mut self,
400 view: Entity<V>,
401 mut update: impl FnMut(&mut V, &mut Window, &mut Context<V>),
402 ) where
403 V: 'static + Render,
404 {
405 let bencher = self.take_bencher("bench_renderer");
406 let window_id = self
407 .with_window(view.entity_id(), |window, _| {
408 window.window_handle().window_id()
409 })
410 .expect("cannot benchmark renderer for entity without a current window");
411
412 let dispatcher = self.background_executor.dispatcher().clone();
413 let collector = FrameTraceScope::start();
414
415 let mut benchmark = || {
416 dispatcher
417 .as_bench()
418 .expect("validated in BenchAppContext::build")
419 .run_ready_main_tasks();
420 self.with_window(view.entity_id(), |window, cx| {
421 view.update(cx, |view, cx| update(view, window, cx));
422 })
423 .expect("cannot benchmark renderer for entity without a current window");
424 // Submit the frame drawn by the update's effect flush, mirroring
425 // production where every drawn frame is presented. With a headless
426 // renderer this includes scene submission to the GPU.
427 self.with_window(view.entity_id(), |window, _| {
428 window.present_if_needed();
429 })
430 .expect("cannot benchmark renderer for entity without a current window");
431 };
432 bencher.iter(&mut benchmark);
433
434 let timings = collector.finish();
435 self.report.record_frame_timings(
436 timings
437 .iter()
438 .filter(|timing| timing.window_id == window_id),
439 );
440 self.replace_bencher(bencher);
441 }
442
443 /// Adds a window with an empty root view for benchmark setup.
444 pub fn add_empty_window(&mut self) -> BenchWindowContext<'a, 'measurement> {
445 let bounds = {
446 let app = self.app.borrow();
447 Bounds::maximized(None, &app)
448 };
449 let window = {
450 let mut app = self.app.borrow_mut();
451 let window: AnyWindowHandle = app
452 .open_window(
453 WindowOptions {
454 window_bounds: Some(WindowBounds::Windowed(bounds)),
455 ..Default::default()
456 },
457 |_, cx| cx.new(|_| Empty),
458 )
459 .expect("failed to open benchmark window")
460 .into();
461 window
462 };
463
464 self.run_until_idle();
465 BenchWindowContext {
466 cx: self.clone(),
467 window,
468 }
469 }
470
471 fn take_bencher(&self, benchmark_kind: &str) -> &'a mut criterion::Bencher<'measurement> {
472 self.bencher.borrow_mut().take().unwrap_or_else(|| {
473 panic!("cannot start {benchmark_kind}: benchmark measurement is already running")
474 })
475 }
476
477 fn replace_bencher(&self, bencher: &'a mut criterion::Bencher<'measurement>) {
478 let previous = self.bencher.borrow_mut().replace(bencher);
479 assert!(
480 previous.is_none(),
481 "benchmark bencher was unexpectedly present after measurement"
482 );
483 }
484
485 /// Runs GPUI benchmark teardown.
486 ///
487 /// Cancels any timers still armed on the shared dispatcher and drains the
488 /// work that cancellation unblocks so they can't fire during a later
489 /// benchmark; assumes no other `BenchAppContext` is live on this thread.
490 pub fn teardown(mut self) {
491 self.run_until_idle();
492 self.update(|cx| {
493 cx.quit();
494 });
495 self.run_until_idle();
496
497 let dispatcher = self.background_executor.dispatcher();
498 let dispatcher = dispatcher
499 .as_bench()
500 .expect("validated in BenchAppContext::build");
501
502 drop(self.app);
503 drop(self.foreground_executor);
504
505 for _ in 0..100 {
506 if dispatcher.cancel_pending_timers() == 0 {
507 return;
508 }
509 dispatcher.run_until_idle();
510 }
511 panic!(
512 "benchmark teardown kept scheduling timers: {}",
513 dispatcher.debug_state()
514 );
515 }
516}
517
518impl AppContext for BenchAppContext<'_, '_> {
519 fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T> {
520 let mut app = self.app.borrow_mut();
521 app.new(build_entity)
522 }
523
524 fn reserve_entity<T: 'static>(&mut self) -> Reservation<T> {
525 let mut app = self.app.borrow_mut();
526 app.reserve_entity()
527 }
528
529 fn insert_entity<T: 'static>(
530 &mut self,
531 reservation: Reservation<T>,
532 build_entity: impl FnOnce(&mut Context<T>) -> T,
533 ) -> Entity<T> {
534 let mut app = self.app.borrow_mut();
535 app.insert_entity(reservation, build_entity)
536 }
537
538 fn update_entity<T: 'static, R>(
539 &mut self,
540 handle: &Entity<T>,
541 update: impl FnOnce(&mut T, &mut Context<T>) -> R,
542 ) -> R {
543 let mut app = self.app.borrow_mut();
544 app.update_entity(handle, update)
545 }
546
547 fn as_mut<'b, T>(&'b mut self, _: &Entity<T>) -> GpuiBorrow<'b, T>
548 where
549 T: 'static,
550 {
551 panic!("Cannot use as_mut with BenchAppContext. Call update() instead.")
552 }
553
554 fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> R
555 where
556 T: 'static,
557 {
558 let app = self.app.borrow();
559 app.read_entity(handle, read)
560 }
561
562 fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
563 where
564 F: FnOnce(AnyView, &mut Window, &mut App) -> T,
565 {
566 let mut app = self.app.borrow_mut();
567 app.update_window(window, update)
568 }
569
570 fn with_window<R>(
571 &mut self,
572 entity_id: EntityId,
573 update: impl FnOnce(&mut Window, &mut App) -> R,
574 ) -> Option<R> {
575 let mut app = self.app.borrow_mut();
576 app.with_window(entity_id, update)
577 }
578
579 fn read_window<T, R>(
580 &self,
581 window: &WindowHandle<T>,
582 read: impl FnOnce(Entity<T>, &App) -> R,
583 ) -> Result<R>
584 where
585 T: 'static,
586 {
587 let app = self.app.borrow();
588 app.read_window(window, read)
589 }
590
591 fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
592 where
593 R: Send + 'static,
594 {
595 self.background_executor.spawn(future)
596 }
597
598 fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
599 where
600 G: Global,
601 {
602 let app = self.app.borrow();
603 app.read_global(callback)
604 }
605}
606
607/// A window-specific context for GPUI benchmarks.
608///
609/// This is separate from `VisualTestContext`; it provides access to a benchmark
610/// window without exposing test-only helpers such as input simulation.
611#[derive(Clone)]
612pub struct BenchWindowContext<'a, 'measurement> {
613 cx: BenchAppContext<'a, 'measurement>,
614 window: AnyWindowHandle,
615}
616
617impl<'a, 'measurement> BenchWindowContext<'a, 'measurement> {
618 /// Returns the underlying benchmark app context.
619 pub fn app_context(&mut self) -> &mut BenchAppContext<'a, 'measurement> {
620 &mut self.cx
621 }
622
623 /// Returns the window associated with this context.
624 pub fn window_handle(&self) -> AnyWindowHandle {
625 self.window
626 }
627
628 /// Runs queued foreground tasks on this thread and waits for in-flight
629 /// background work to finish. Pending timers are not waited for.
630 pub fn run_until_idle(&self) {
631 self.cx.run_until_idle();
632 }
633
634 /// Updates the benchmark window.
635 pub fn update<R>(&mut self, update: impl FnOnce(&mut Window, &mut App) -> R) -> R {
636 self.cx
637 .update_window(self.window, |_, window, cx| update(window, cx))
638 .expect("benchmark window was unexpectedly closed")
639 }
640}
641
642impl AppContext for BenchWindowContext<'_, '_> {
643 fn new<T: 'static>(&mut self, build_entity: impl FnOnce(&mut Context<T>) -> T) -> Entity<T> {
644 self.window
645 .update(&mut self.cx, |_, _, cx| cx.new(build_entity))
646 .expect("benchmark window was unexpectedly closed")
647 }
648
649 fn reserve_entity<T: 'static>(&mut self) -> Reservation<T> {
650 self.cx.reserve_entity()
651 }
652
653 fn insert_entity<T: 'static>(
654 &mut self,
655 reservation: Reservation<T>,
656 build_entity: impl FnOnce(&mut Context<T>) -> T,
657 ) -> Entity<T> {
658 self.window
659 .update(&mut self.cx, |_, _, cx| {
660 cx.insert_entity(reservation, build_entity)
661 })
662 .expect("benchmark window was unexpectedly closed")
663 }
664
665 fn update_entity<T: 'static, R>(
666 &mut self,
667 handle: &Entity<T>,
668 update: impl FnOnce(&mut T, &mut Context<T>) -> R,
669 ) -> R {
670 self.cx.update_entity(handle, update)
671 }
672
673 fn as_mut<'b, T>(&'b mut self, handle: &Entity<T>) -> GpuiBorrow<'b, T>
674 where
675 T: 'static,
676 {
677 self.cx.as_mut(handle)
678 }
679
680 fn read_entity<T, R>(&self, handle: &Entity<T>, read: impl FnOnce(&T, &App) -> R) -> R
681 where
682 T: 'static,
683 {
684 self.cx.read_entity(handle, read)
685 }
686
687 fn update_window<T, F>(&mut self, window: AnyWindowHandle, update: F) -> Result<T>
688 where
689 F: FnOnce(AnyView, &mut Window, &mut App) -> T,
690 {
691 self.cx.update_window(window, update)
692 }
693
694 fn with_window<R>(
695 &mut self,
696 entity_id: EntityId,
697 update: impl FnOnce(&mut Window, &mut App) -> R,
698 ) -> Option<R> {
699 self.cx.with_window(entity_id, update)
700 }
701
702 fn read_window<T, R>(
703 &self,
704 window: &WindowHandle<T>,
705 read: impl FnOnce(Entity<T>, &App) -> R,
706 ) -> Result<R>
707 where
708 T: 'static,
709 {
710 self.cx.read_window(window, read)
711 }
712
713 fn background_spawn<R>(&self, future: impl Future<Output = R> + Send + 'static) -> Task<R>
714 where
715 R: Send + 'static,
716 {
717 self.cx.background_spawn(future)
718 }
719
720 fn read_global<G, R>(&self, callback: impl FnOnce(&G, &App) -> R) -> R
721 where
722 G: Global,
723 {
724 self.cx.read_global(callback)
725 }
726}
727
728impl VisualContext for BenchWindowContext<'_, '_> {
729 type Result<T> = Result<T>;
730
731 fn window_handle(&self) -> AnyWindowHandle {
732 self.window
733 }
734
735 fn update_window_entity<T: 'static, R>(
736 &mut self,
737 entity: &Entity<T>,
738 update: impl FnOnce(&mut T, &mut Window, &mut Context<T>) -> R,
739 ) -> Result<R> {
740 let entity = entity.clone();
741 self.cx
742 .app
743 .borrow_mut()
744 .with_window(entity.entity_id(), |window, app| {
745 entity.update(app, |entity, cx| update(entity, window, cx))
746 })
747 .ok_or_else(|| {
748 anyhow!("entity has no current window; use `update` instead of `update_in`")
749 })
750 }
751
752 fn new_window_entity<T: 'static>(
753 &mut self,
754 build_entity: impl FnOnce(&mut Window, &mut Context<T>) -> T,
755 ) -> Result<Entity<T>> {
756 self.window.update(&mut self.cx, |_, window, cx| {
757 cx.new(|cx| build_entity(window, cx))
758 })
759 }
760
761 fn replace_root_view<V>(
762 &mut self,
763 build_view: impl FnOnce(&mut Window, &mut Context<V>) -> V,
764 ) -> Result<Entity<V>>
765 where
766 V: 'static + Render,
767 {
768 self.window.update(&mut self.cx, |_, window, cx| {
769 window.replace_root(cx, build_view)
770 })
771 }
772
773 fn focus<V>(&mut self, entity: &Entity<V>) -> Result<()>
774 where
775 V: Focusable,
776 {
777 self.window.update(&mut self.cx, |_, window, cx| {
778 entity.read(cx).focus_handle(cx).focus(window, cx)
779 })
780 }
781}
782