Skip to main content

gpui/
platform_entry.rs

1//! Platform entrypoints: construct the default [`crate::Platform`] for the
2//! current operating system and turn it into an [`crate::Application`].
3//!
4//! These live behind the `platform` feature, which pulls in the
5//! backend for the target OS.
6
7use crate::{Application, BackgroundExecutor, Platform};
8use std::rc::Rc;
9
10/// Returns a background executor for the current platform.
11pub fn background_executor() -> BackgroundExecutor {
12    current_platform(true).background_executor()
13}
14
15/// Returns an [`Application`] backed by the default platform for this OS.
16pub fn application() -> Application {
17    #[cfg(target_family = "wasm")]
18    {
19        application_with_web_backend(gpui_web::WebBackendPreference::Auto)
20    }
21
22    #[cfg(not(target_family = "wasm"))]
23    Application::with_platform(current_platform(false))
24}
25
26/// Returns a headless [`Application`] backed by the default platform for this OS.
27pub fn headless() -> Application {
28    Application::with_platform(current_platform(true))
29}
30
31#[cfg(target_family = "wasm")]
32pub use gpui_web::WebBackendPreference;
33
34/// The producer's half of the imported-texture path on Windows: a producer reaches the Direct3D
35/// renderer's device through a `gpu_canvas` callback
36/// ([`GpuCanvasContext::device`](crate::GpuCanvasContext::device)), and a texture made on it becomes
37/// a token through this extension.
38#[cfg(target_os = "windows")]
39pub use gpui_windows::{DirectXImportedTexture, DirectXRenderer, DirectXTextureExt};
40
41/// The producer's half of the imported-texture path on macOS, the same shape as the Windows pair above: the device
42/// comes from the canvas's `device::<R>()`, and a texture made on it becomes a token through this extension.
43/// Unlike the Windows one it names no payload type of its own -- the engine's MetalTexture is the
44/// handle the renderer resolves.
45#[cfg(target_os = "macos")]
46pub use gpui_macos::MetalTextureExt;
47
48/// The producer's half of the imported-texture path where wgpu is the renderer, which is the platform's own default on
49/// these two targets: the same shape, from the crate that re-exports the wgpu renderer. A window on
50/// Windows whose factory installed `WgpuRenderer` takes the trait from `gpui_wgpu` instead, which
51/// is the crate that renderer comes from.
52#[cfg(any(target_os = "linux", target_os = "freebsd"))]
53pub use gpui_linux::ImportedTextureExt;
54
55#[cfg(target_family = "wasm")]
56pub fn application_with_web_backend(backend_preference: WebBackendPreference) -> Application {
57    let platform = Rc::new(gpui_web::WebPlatform::new_with_backend(
58        true,
59        backend_preference,
60    ));
61    let http_client = std::sync::Arc::new(platform.fetch_http_client());
62    Application::with_platform(platform).with_http_client(http_client)
63}
64
65/// Unlike `application`, this function returns a single-threaded web application.
66#[cfg(target_family = "wasm")]
67pub fn single_threaded_web() -> Application {
68    let platform = Rc::new(gpui_web::WebPlatform::new(false));
69    let http_client = std::sync::Arc::new(platform.fetch_http_client());
70    Application::with_platform(platform).with_http_client(http_client)
71}
72
73/// Initializes logging for the web platform. `gpui_web::init_logging` also
74/// installs the panic hook. Call this before running the application in a
75/// wasm_bindgen entrypoint.
76#[cfg(target_family = "wasm")]
77pub fn web_init() {
78    gpui_web::init_logging();
79}
80
81/// Returns the default [`Platform`] for the current OS.
82pub fn current_platform(headless: bool) -> Rc<dyn Platform> {
83    #[cfg(target_os = "macos")]
84    {
85        Rc::new(gpui_macos::MacPlatform::new(headless))
86    }
87
88    #[cfg(target_os = "windows")]
89    {
90        Rc::new(
91            gpui_windows::WindowsPlatform::new(headless)
92                .expect("failed to initialize Windows platform"),
93        )
94    }
95
96    #[cfg(any(target_os = "linux", target_os = "freebsd"))]
97    {
98        gpui_linux::current_platform(headless)
99    }
100
101    #[cfg(target_family = "wasm")]
102    {
103        let _ = headless;
104        Rc::new(gpui_web::WebPlatform::new(true))
105    }
106}
107
108/// Returns a new [`crate::SceneRenderer`] for the current platform, if available.
109#[cfg(any(feature = "bench-support", feature = "test-support"))]
110pub fn current_headless_renderer() -> anyhow::Result<Option<Box<dyn crate::SceneRenderer>>> {
111    #[cfg(all(feature = "platform", any(target_os = "linux", target_os = "freebsd")))]
112    {
113        // Headless has no platform instance to ask, so the platform crate supplies its debug
114        // preference directly: the same `ZED_DEVICE_ID` lowering a windowed run gets through
115        // `set_gpu_preference`.
116        current_headless_renderer_with(
117            gpui_linux::gpu_preference_from_env().unwrap_or(crate::GpuPreference::Auto),
118        )
119    }
120
121    #[cfg(not(all(feature = "platform", any(target_os = "linux", target_os = "freebsd"))))]
122    {
123        current_headless_renderer_with(crate::GpuPreference::Auto)
124    }
125}
126
127/// As [`current_headless_renderer`], selecting the adapter by `gpu_preference` where the headless
128/// renderer is `wgpu`.
129///
130/// Metal's headless renderer takes no preference, and a platform with no headless renderer returns
131/// `Ok(None)`; the argument is ignored in both cases. The wgpu context is cached process-wide, so
132/// only the first renderer's preference takes effect.
133#[cfg(any(feature = "bench-support", feature = "test-support"))]
134pub fn current_headless_renderer_with(
135    gpu_preference: crate::GpuPreference,
136) -> anyhow::Result<Option<Box<dyn crate::SceneRenderer>>> {
137    let _ = &gpu_preference;
138
139    #[cfg(target_os = "macos")]
140    {
141        Ok(Some(Box::new(
142            gpui_macos::metal_renderer::MetalHeadlessRenderer::new(),
143        )))
144    }
145
146    #[cfg(all(feature = "platform", any(target_os = "linux", target_os = "freebsd")))]
147    {
148        // The Linux platform owns no renderer of its own: wgpu is the renderer a window uses here,
149        // so a headless context renders through the same code a window drives.
150        gpui_linux::WgpuHeadlessRenderer::with_gpu_preference(gpu_preference)
151            .map(|renderer| Some(Box::new(renderer) as Box<dyn crate::SceneRenderer>))
152    }
153
154    #[cfg(not(any(
155        target_os = "macos",
156        all(feature = "platform", any(target_os = "linux", target_os = "freebsd")),
157    )))]
158    {
159        Ok(None)
160    }
161}
162
163/// Returns the current platform's text system for benchmark contexts, built once per
164/// thread and shared by every context on it.
165///
166/// `#[gpui::bench]` creates a context for every Criterion routine call. Building a whole
167/// platform each time only to take its text system leaks the platform's threads on Linux
168/// (a 20-worker dispatcher and a timer thread per call), which slows every later
169/// benchmark in the process.
170#[cfg(feature = "bench-support")]
171pub fn bench_text_system() -> std::sync::Arc<dyn crate::PlatformTextSystem> {
172    thread_local! {
173        static TEXT_SYSTEM: std::sync::Arc<dyn crate::PlatformTextSystem> =
174            current_platform(true).text_system();
175    }
176    TEXT_SYSTEM.with(|text_system| text_system.clone())
177}
178
179#[cfg(all(test, target_os = "macos"))]
180mod tests {
181    use super::*;
182    use crate::{AppContext, Empty, VisualTestAppContext};
183    use std::cell::RefCell;
184    use std::time::Duration;
185
186    // Note: All VisualTestAppContext tests are ignored by default because they require
187    // the macOS main thread. Standard Rust tests run on worker threads, which causes
188    // SIGABRT when interacting with macOS AppKit/Cocoa APIs.
189    //
190    // To run these tests, use:
191    // cargo test -p gpui visual_test_context -- --ignored --test-threads=1
192
193    #[test]
194    #[ignore] // Requires macOS main thread
195    fn test_foreground_tasks_run_with_run_until_parked() {
196        let mut cx = VisualTestAppContext::new(current_platform(false));
197
198        let task_ran = Rc::new(RefCell::new(false));
199
200        // Spawn a foreground task via the App's spawn method
201        // This should use our TestDispatcher, not the MacDispatcher
202        {
203            let task_ran = task_ran.clone();
204            cx.update(|cx| {
205                cx.spawn(async move |_| {
206                    *task_ran.borrow_mut() = true;
207                })
208                .detach();
209            });
210        }
211
212        // The task should not have run yet
213        assert!(!*task_ran.borrow());
214
215        // Run until parked should execute the foreground task
216        cx.run_until_parked();
217
218        // Now the task should have run
219        assert!(*task_ran.borrow());
220    }
221
222    #[test]
223    #[ignore] // Requires macOS main thread
224    fn test_advance_clock_triggers_delayed_tasks() {
225        let mut cx = VisualTestAppContext::new(current_platform(false));
226
227        let task_ran = Rc::new(RefCell::new(false));
228
229        // Spawn a task that waits for a timer
230        {
231            let task_ran = task_ran.clone();
232            let executor = cx.background_executor.clone();
233            cx.update(|cx| {
234                cx.spawn(async move |_| {
235                    executor.timer(Duration::from_millis(500)).await;
236                    *task_ran.borrow_mut() = true;
237                })
238                .detach();
239            });
240        }
241
242        // Run until parked - the task should be waiting on the timer
243        cx.run_until_parked();
244        assert!(!*task_ran.borrow());
245
246        // Advance clock past the timer duration
247        cx.advance_clock(Duration::from_millis(600));
248
249        // Now the task should have completed
250        assert!(*task_ran.borrow());
251    }
252
253    #[test]
254    #[ignore] // Requires macOS main thread - window creation fails on test threads
255    fn test_window_spawn_uses_test_dispatcher() {
256        let mut cx = VisualTestAppContext::new(current_platform(false));
257
258        let task_ran = Rc::new(RefCell::new(false));
259
260        let window = cx
261            .open_offscreen_window_default(|_, cx| cx.new(|_| Empty))
262            .expect("Failed to open window");
263
264        // Spawn a task via window.spawn - this is the critical test case
265        // for tooltip behavior, as tooltips use window.spawn for delayed show
266        {
267            let task_ran = task_ran.clone();
268            cx.update_window(window.into(), |_, window, cx| {
269                window
270                    .spawn(cx, async move |_| {
271                        *task_ran.borrow_mut() = true;
272                    })
273                    .detach();
274            })
275            .ok();
276        }
277
278        // The task should not have run yet
279        assert!(!*task_ran.borrow());
280
281        // Run until parked should execute the foreground task spawned via window
282        cx.run_until_parked();
283
284        // Now the task should have run
285        assert!(*task_ran.borrow());
286    }
287}