gpui(windows): Reduce timer resolution to 1ms (#48364)

Lukas Wirth created

Otherwise block_with_timeout is effectively useless on windows as we
would block a minimum of 15ms which is our entire frame budget

Release Notes:

- N/A *or* Added/Fixed/Improved ...

Change summary

Cargo.toml                                   |  1 
crates/editor/src/indent_guides.rs           |  2 
crates/gpui/src/platform/windows/platform.rs |  6 +++++
crates/gpui/src/platform_scheduler.rs        | 24 ++++++++++-----------
crates/language/src/buffer.rs                | 20 +++++++++++++----
5 files changed, 34 insertions(+), 19 deletions(-)

Detailed changes

Cargo.toml 🔗

@@ -791,6 +791,7 @@ features = [
     "Win32_UI_Shell_Common",
     "Win32_UI_Shell_PropertiesSystem",
     "Win32_UI_WindowsAndMessaging",
+    "Win32_Media",
 ]
 
 [patch.crates-io]

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -20,6 +20,7 @@ use windows::{
     Win32::{
         Foundation::*,
         Graphics::{Direct3D11::ID3D11Device, Gdi::*},
+        Media::{timeBeginPeriod, timeEndPeriod},
         Security::Credentials::*,
         System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*},
         UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
@@ -97,6 +98,10 @@ impl WindowsPlatform {
     pub(crate) fn new(headless: bool) -> Result<Self> {
         unsafe {
             OleInitialize(None).context("unable to initialize Windows OLE")?;
+            // Set the system timer resolution to 1ms so that short timeouts
+            // (e.g. in Scheduler::block) are not rounded up to the default
+            // ~15.6ms tick interval.
+            timeBeginPeriod(1);
         }
         let (directx_devices, text_system, direct_write_text_system) = if !headless {
             let devices = DirectXDevices::new().context("Creating DirectX devices")?;
@@ -980,6 +985,7 @@ impl WindowsPlatformInner {
 impl Drop for WindowsPlatform {
     fn drop(&mut self) {
         unsafe {
+            timeEndPeriod(1);
             DestroyWindow(self.handle)
                 .context("Destroying platform window")
                 .log_err();

crates/gpui/src/platform_scheduler.rs 🔗

@@ -53,21 +53,19 @@ impl Scheduler for PlatformScheduler {
             unparker.unpark();
         });
         let mut cx = Context::from_waker(&waker);
-
+        if let Poll::Ready(()) = future.as_mut().poll(&mut cx) {
+            return true;
+        }
         loop {
-            match future.as_mut().poll(&mut cx) {
-                Poll::Ready(()) => return true,
-                Poll::Pending => {
-                    if let Some(deadline) = deadline {
-                        let now = Instant::now();
-                        if now >= deadline {
-                            return false;
-                        }
-                        parker.park_timeout(deadline - now);
-                    } else {
-                        parker.park();
-                    }
+            match deadline {
+                Some(deadline) if !parker.park_deadline(deadline) && deadline <= Instant::now() => {
+                    return false;
                 }
+                Some(_) => (),
+                None => parker.park(),
+            }
+            if let Poll::Ready(()) = future.as_mut().poll(&mut cx) {
+                break true;
             }
         }
     }

crates/language/src/buffer.rs 🔗

@@ -1842,7 +1842,7 @@ impl Buffer {
                 language.clone(),
                 sync_parse_timeout,
             ) {
-                self.did_finish_parsing(syntax_snapshot, Duration::from_millis(300), cx);
+                self.did_finish_parsing(syntax_snapshot, Some(Duration::from_millis(300)), cx);
                 self.reparse = None;
                 return;
             }
@@ -1874,7 +1874,7 @@ impl Buffer {
                 let parse_again = this.version.changed_since(&parsed_version)
                     || language_registry_changed()
                     || grammar_changed();
-                this.did_finish_parsing(new_syntax_map, Duration::ZERO, cx);
+                this.did_finish_parsing(new_syntax_map, None, cx);
                 this.reparse = None;
                 if parse_again {
                     this.reparse(cx, false);
@@ -1887,7 +1887,7 @@ impl Buffer {
     fn did_finish_parsing(
         &mut self,
         syntax_snapshot: SyntaxSnapshot,
-        block_budget: Duration,
+        block_budget: Option<Duration>,
         cx: &mut Context<Self>,
     ) {
         self.non_text_state_update_count += 1;
@@ -1951,9 +1951,19 @@ impl Buffer {
         }
     }
 
-    fn request_autoindent(&mut self, cx: &mut Context<Self>, block_budget: Duration) {
+    fn request_autoindent(&mut self, cx: &mut Context<Self>, block_budget: Option<Duration>) {
         if let Some(indent_sizes) = self.compute_autoindents() {
             let indent_sizes = cx.background_spawn(indent_sizes);
+            let Some(block_budget) = block_budget else {
+                self.pending_autoindent = Some(cx.spawn(async move |this, cx| {
+                    let indent_sizes = indent_sizes.await;
+                    this.update(cx, |this, cx| {
+                        this.apply_autoindents(indent_sizes, cx);
+                    })
+                    .ok();
+                }));
+                return;
+            };
             match cx
                 .foreground_executor()
                 .block_with_timeout(block_budget, indent_sizes)
@@ -2861,7 +2871,7 @@ impl Buffer {
             is_block_mode: false,
             ignore_empty_lines: true,
         }));
-        self.request_autoindent(cx, Duration::from_micros(300));
+        self.request_autoindent(cx, Some(Duration::from_micros(300)));
     }
 
     // Inserts newlines at the given position to create an empty line, returning the start of the new line.