Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T06:41:43.242Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

bench_context.rs

782 lines · 26.8 KB · rust
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
Served at tenant.openagents/omega Member data and write actions are omitted.