linux: implement dispatcher, add dummy textsystem

Dzmitry Malyshau created

Change summary

Cargo.lock                                   | 13 ++
crates/gpui/Cargo.toml                       |  3 
crates/gpui/src/platform.rs                  |  8 
crates/gpui/src/platform/linux.rs            |  2 
crates/gpui/src/platform/linux/dispatcher.rs | 97 ++++++++++++++++++++++
crates/gpui/src/platform/linux/platform.rs   | 15 ++-
crates/gpui/src/platform/test.rs             |  2 
crates/gpui/src/platform/test/platform.rs    |  6 
crates/gpui/src/platform/test/text_system.rs | 58 +++++++++++++
crates/gpui/src/window/element_cx.rs         |  4 
10 files changed, 194 insertions(+), 14 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -2732,6 +2732,7 @@ checksum = "55ac459de2512911e4b674ce33cf20befaba382d05b62b008afc1c8b57cbf181"
 dependencies = [
  "futures-core",
  "futures-sink",
+ "nanorand",
  "spin 0.9.8",
 ]
 
@@ -3105,8 +3106,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
 dependencies = [
  "cfg-if 1.0.0",
+ "js-sys",
  "libc",
  "wasi 0.11.0+wasi-snapshot-preview1",
+ "wasm-bindgen",
 ]
 
 [[package]]
@@ -3232,6 +3235,7 @@ dependencies = [
  "dhat",
  "env_logger",
  "etagere",
+ "flume",
  "font-kit",
  "foreign-types 0.3.2",
  "futures 0.3.28",
@@ -4644,6 +4648,15 @@ dependencies = [
  "rand 0.8.5",
 ]
 
+[[package]]
+name = "nanorand"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3"
+dependencies = [
+ "getrandom 0.2.10",
+]
+
 [[package]]
 name = "native-tls"
 version = "0.2.11"

crates/gpui/Cargo.toml 🔗

@@ -94,3 +94,6 @@ log.workspace = true
 media = { path = "../media" }
 metal = "0.21.0"
 objc = "0.2"
+
+[target.'cfg(target_os = "linux")'.dependencies]
+flume = "0.11"

crates/gpui/src/platform.rs 🔗

@@ -1,9 +1,9 @@
 mod app_menu;
 mod keystroke;
-#[cfg(target_os = "macos")]
-mod mac;
 #[cfg(target_os = "linux")]
 mod linux;
+#[cfg(target_os = "macos")]
+mod mac;
 #[cfg(any(test, feature = "test-support"))]
 mod test;
 
@@ -35,10 +35,10 @@ use uuid::Uuid;
 
 pub use app_menu::*;
 pub use keystroke::*;
-#[cfg(target_os = "macos")]
-pub(crate) use mac::*;
 #[cfg(target_os = "linux")]
 pub(crate) use linux::*;
+#[cfg(target_os = "macos")]
+pub(crate) use mac::*;
 #[cfg(any(test, feature = "test-support"))]
 pub(crate) use test::*;
 use time::UtcOffset;

crates/gpui/src/platform/linux/dispatcher.rs 🔗

@@ -0,0 +1,97 @@
+#![allow(non_upper_case_globals)]
+#![allow(non_camel_case_types)]
+#![allow(non_snake_case)]
+
+use crate::{PlatformDispatcher, TaskLabel};
+use async_task::Runnable;
+use parking::{Parker, Unparker};
+use parking_lot::Mutex;
+use std::{
+    panic, thread,
+    time::{Duration, Instant},
+};
+
+pub(crate) struct LinuxDispatcher {
+    parker: Mutex<Parker>,
+    timed_tasks: Mutex<Vec<(Instant, Runnable)>>,
+    main_sender: flume::Sender<Runnable>,
+    main_receiver: flume::Receiver<Runnable>,
+    background_sender: flume::Sender<Runnable>,
+    background_thread: thread::JoinHandle<()>,
+    main_thread_id: thread::ThreadId,
+}
+
+impl Default for LinuxDispatcher {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl LinuxDispatcher {
+    pub fn new() -> Self {
+        let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
+        let (background_sender, background_receiver) = flume::unbounded::<Runnable>();
+        let background_thread = thread::spawn(move || {
+            for runnable in background_receiver {
+                let _ignore_panic = panic::catch_unwind(|| runnable.run());
+            }
+        });
+        LinuxDispatcher {
+            parker: Mutex::new(Parker::new()),
+            timed_tasks: Mutex::new(Vec::new()),
+            main_sender,
+            main_receiver,
+            background_sender,
+            background_thread,
+            main_thread_id: thread::current().id(),
+        }
+    }
+}
+
+impl PlatformDispatcher for LinuxDispatcher {
+    fn is_main_thread(&self) -> bool {
+        thread::current().id() == self.main_thread_id
+    }
+
+    fn dispatch(&self, runnable: Runnable, _: Option<TaskLabel>) {
+        self.background_sender.send(runnable).unwrap();
+    }
+
+    fn dispatch_on_main_thread(&self, runnable: Runnable) {
+        self.main_sender.send(runnable).unwrap();
+    }
+
+    fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
+        let moment = Instant::now() + duration;
+        let mut timed_tasks = self.timed_tasks.lock();
+        timed_tasks.push((moment, runnable));
+        timed_tasks.sort_unstable_by(|&(ref a, _), &(ref b, _)| b.cmp(a));
+    }
+
+    fn tick(&self, background_only: bool) -> bool {
+        let mut ran = false;
+        if self.is_main_thread() && !background_only {
+            for runnable in self.main_receiver.try_iter() {
+                runnable.run();
+                ran = true;
+            }
+        }
+        let mut timed_tasks = self.timed_tasks.lock();
+        while let Some(&(moment, _)) = timed_tasks.last() {
+            if moment <= Instant::now() {
+                let (_, runnable) = timed_tasks.pop().unwrap();
+                runnable.run();
+                ran = true;
+            }
+        }
+        ran
+    }
+
+    fn park(&self) {
+        self.parker.lock().park()
+    }
+
+    fn unparker(&self) -> Unparker {
+        self.parker.lock().unparker()
+    }
+}

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

@@ -2,8 +2,9 @@
 
 use crate::{
     Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId,
-    Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
-    PlatformTextSystem, PlatformWindow, Result, SemanticVersion, Task, WindowOptions,
+    ForegroundExecutor, Keymap, LinuxDispatcher, Menu, PathPromptOptions, Platform,
+    PlatformDisplay, PlatformInput, PlatformTextSystem, PlatformWindow, Result, SemanticVersion,
+    Task, WindowOptions,
 };
 
 use futures::channel::oneshot;
@@ -17,10 +18,11 @@ use std::{
 };
 use time::UtcOffset;
 
-
 pub(crate) struct LinuxPlatform(Mutex<LinuxPlatformState>);
 
 pub(crate) struct LinuxPlatformState {
+    background_executor: BackgroundExecutor,
+    foreground_executor: ForegroundExecutor,
 }
 
 impl Default for LinuxPlatform {
@@ -31,18 +33,21 @@ impl Default for LinuxPlatform {
 
 impl LinuxPlatform {
     pub(crate) fn new() -> Self {
+        let dispatcher = Arc::new(LinuxDispatcher::new());
         Self(Mutex::new(LinuxPlatformState {
+            background_executor: BackgroundExecutor::new(dispatcher.clone()),
+            foreground_executor: ForegroundExecutor::new(dispatcher),
         }))
     }
 }
 
 impl Platform for LinuxPlatform {
     fn background_executor(&self) -> BackgroundExecutor {
-        unimplemented!()
+        self.0.lock().background_executor.clone()
     }
 
     fn foreground_executor(&self) -> crate::ForegroundExecutor {
-        unimplemented!()
+        self.0.lock().foreground_executor.clone()
     }
 
     fn text_system(&self) -> Arc<dyn PlatformTextSystem> {

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

@@ -1,9 +1,11 @@
 mod dispatcher;
 mod display;
 mod platform;
+mod text_system;
 mod window;
 
 pub(crate) use dispatcher::*;
 pub(crate) use display::*;
 pub(crate) use platform::*;
+pub(crate) use text_system::*;
 pub(crate) use window::*;

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

@@ -1,7 +1,7 @@
 use crate::{
     AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, DisplayId, ForegroundExecutor,
-    Keymap, Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestWindow,
-    WindowOptions,
+    Keymap, Platform, PlatformDisplay, PlatformTextSystem, Task, TestDisplay, TestTextSystem,
+    TestWindow, WindowOptions,
 };
 use anyhow::{anyhow, Result};
 use collections::VecDeque;
@@ -118,7 +118,7 @@ impl Platform for TestPlatform {
     }
 
     fn text_system(&self) -> Arc<dyn PlatformTextSystem> {
-        Arc::new(crate::platform::mac::MacTextSystem::new())
+        Arc::new(TestTextSystem {})
     }
 
     fn run(&self, _on_finish_launching: Box<dyn FnOnce()>) {

crates/gpui/src/platform/test/text_system.rs 🔗

@@ -0,0 +1,58 @@
+use crate::{
+    Bounds, DevicePixels, Font, FontId, FontMetrics, FontRun, GlyphId, LineLayout, Pixels,
+    PlatformTextSystem, RenderGlyphParams, Size,
+};
+use anyhow::Result;
+use std::sync::Arc;
+
+pub(crate) struct TestTextSystem {}
+
+#[allow(unused)]
+impl PlatformTextSystem for TestTextSystem {
+    fn add_fonts(&self, fonts: &[Arc<Vec<u8>>]) -> Result<()> {
+        unimplemented!()
+    }
+    fn all_font_names(&self) -> Vec<String> {
+        unimplemented!()
+    }
+    fn all_font_families(&self) -> Vec<String> {
+        unimplemented!()
+    }
+    fn font_id(&self, descriptor: &Font) -> Result<FontId> {
+        unimplemented!()
+    }
+    fn font_metrics(&self, font_id: FontId) -> FontMetrics {
+        unimplemented!()
+    }
+    fn typographic_bounds(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Bounds<f32>> {
+        unimplemented!()
+    }
+    fn advance(&self, font_id: FontId, glyph_id: GlyphId) -> Result<Size<f32>> {
+        unimplemented!()
+    }
+    fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
+        unimplemented!()
+    }
+    fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
+        unimplemented!()
+    }
+    fn rasterize_glyph(
+        &self,
+        params: &RenderGlyphParams,
+        raster_bounds: Bounds<DevicePixels>,
+    ) -> Result<(Size<DevicePixels>, Vec<u8>)> {
+        unimplemented!()
+    }
+    fn layout_line(&self, text: &str, font_size: Pixels, runs: &[FontRun]) -> LineLayout {
+        unimplemented!()
+    }
+    fn wrap_line(
+        &self,
+        text: &str,
+        font_id: FontId,
+        font_size: Pixels,
+        width: Pixels,
+    ) -> Vec<usize> {
+        unimplemented!()
+    }
+}

crates/gpui/src/window/element_cx.rs 🔗

@@ -35,8 +35,8 @@ use crate::{
     InputHandler, IsZero, KeyContext, KeyEvent, KeymatchMode, LayoutId, MonochromeSprite,
     MouseEvent, PaintQuad, Path, Pixels, PlatformInputHandler, Point, PolychromeSprite, Quad,
     RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene, Shadow, SharedString, Size,
-    StackingContext, StackingOrder, Style, TextStyleRefinement, Underline, UnderlineStyle,
-    Window, WindowContext, SUBPIXEL_VARIANTS,
+    StackingContext, StackingOrder, Style, TextStyleRefinement, Underline, UnderlineStyle, Window,
+    WindowContext, SUBPIXEL_VARIANTS,
 };
 
 type AnyMouseListener = Box<dyn FnMut(&dyn Any, DispatchPhase, &mut ElementContext) + 'static>;