linux: create a hidden window inside the platform

It allows us to receive messages from the dispatcher,
which breaks us out of waiting and lets us execute
main thread runnables as a part of the main loop.
This commit is contained in:
Dzmitry Malyshau 2024-02-04 23:52:22 -08:00
parent 282cc71df9
commit 521b2b12e4
4 changed files with 80 additions and 39 deletions

View file

@ -7,29 +7,50 @@ use async_task::Runnable;
use parking::{Parker, Unparker}; use parking::{Parker, Unparker};
use parking_lot::Mutex; use parking_lot::Mutex;
use std::{ use std::{
panic, thread, panic,
sync::Arc,
thread,
time::{Duration, Instant}, time::{Duration, Instant},
}; };
use xcb::x;
pub(crate) struct LinuxDispatcher { pub(crate) struct LinuxDispatcher {
xcb_connection: Arc<xcb::Connection>,
x_listener_window: x::Window,
parker: Mutex<Parker>, parker: Mutex<Parker>,
timed_tasks: Mutex<Vec<(Instant, Runnable)>>, timed_tasks: Mutex<Vec<(Instant, Runnable)>>,
main_sender: flume::Sender<Runnable>, main_sender: flume::Sender<Runnable>,
main_receiver: flume::Receiver<Runnable>,
background_sender: flume::Sender<Runnable>, background_sender: flume::Sender<Runnable>,
background_thread: thread::JoinHandle<()>, _background_thread: thread::JoinHandle<()>,
main_thread_id: thread::ThreadId, main_thread_id: thread::ThreadId,
} }
impl Default for LinuxDispatcher {
fn default() -> Self {
Self::new()
}
}
impl LinuxDispatcher { impl LinuxDispatcher {
pub fn new() -> Self { pub fn new(
let (main_sender, main_receiver) = flume::unbounded::<Runnable>(); main_sender: flume::Sender<Runnable>,
xcb_connection: &Arc<xcb::Connection>,
x_root_index: i32,
) -> Self {
let x_listener_window = xcb_connection.generate_id();
let screen = xcb_connection
.get_setup()
.roots()
.nth(x_root_index as usize)
.unwrap();
xcb_connection.send_request(&x::CreateWindow {
depth: 0,
wid: x_listener_window,
parent: screen.root(),
x: 0,
y: 0,
width: 1,
height: 1,
border_width: 0,
class: x::WindowClass::InputOnly,
visual: screen.root_visual(),
value_list: &[],
});
let (background_sender, background_receiver) = flume::unbounded::<Runnable>(); let (background_sender, background_receiver) = flume::unbounded::<Runnable>();
let background_thread = thread::spawn(move || { let background_thread = thread::spawn(move || {
for runnable in background_receiver { for runnable in background_receiver {
@ -37,21 +58,23 @@ impl LinuxDispatcher {
} }
}); });
LinuxDispatcher { LinuxDispatcher {
xcb_connection: Arc::clone(xcb_connection),
x_listener_window,
parker: Mutex::new(Parker::new()), parker: Mutex::new(Parker::new()),
timed_tasks: Mutex::new(Vec::new()), timed_tasks: Mutex::new(Vec::new()),
main_sender, main_sender,
main_receiver,
background_sender, background_sender,
background_thread, _background_thread: background_thread,
main_thread_id: thread::current().id(), main_thread_id: thread::current().id(),
} }
} }
}
pub fn tick_main(&self) { impl Drop for LinuxDispatcher {
assert!(self.is_main_thread()); fn drop(&mut self) {
if let Ok(runnable) = self.main_receiver.try_recv() { self.xcb_connection.send_request(&x::DestroyWindow {
runnable.run(); window: self.x_listener_window,
} });
} }
} }
@ -66,6 +89,18 @@ impl PlatformDispatcher for LinuxDispatcher {
fn dispatch_on_main_thread(&self, runnable: Runnable) { fn dispatch_on_main_thread(&self, runnable: Runnable) {
self.main_sender.send(runnable).unwrap(); self.main_sender.send(runnable).unwrap();
// Send a message to the invisible window, forcing
// tha main loop to wake up and dispatch the runnable.
self.xcb_connection.send_request(&x::SendEvent {
propagate: false,
destination: x::SendEventDest::Window(self.x_listener_window),
event_mask: x::EventMask::NO_EVENT,
event: &x::VisibilityNotifyEvent::new(
self.x_listener_window,
x::Visibility::Unobscured,
),
});
self.xcb_connection.flush().unwrap();
} }
fn dispatch_after(&self, duration: Duration, runnable: Runnable) { fn dispatch_after(&self, duration: Duration, runnable: Runnable) {
@ -76,24 +111,17 @@ impl PlatformDispatcher for LinuxDispatcher {
} }
fn tick(&self, background_only: bool) -> bool { 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(); let mut timed_tasks = self.timed_tasks.lock();
let old_count = timed_tasks.len();
while let Some(&(moment, _)) = timed_tasks.last() { while let Some(&(moment, _)) = timed_tasks.last() {
if moment <= Instant::now() { if moment <= Instant::now() {
let (_, runnable) = timed_tasks.pop().unwrap(); let (_, runnable) = timed_tasks.pop().unwrap();
runnable.run(); runnable.run();
ran = true;
} else { } else {
break; break;
} }
} }
ran timed_tasks.len() != old_count
} }
fn park(&self) { fn park(&self) {

View file

@ -7,6 +7,7 @@ use crate::{
PlatformTextSystem, PlatformWindow, Point, Result, SemanticVersion, Size, Task, WindowOptions, PlatformTextSystem, PlatformWindow, Point, Result, SemanticVersion, Size, Task, WindowOptions,
}; };
use async_task::Runnable;
use collections::{HashMap, HashSet}; use collections::{HashMap, HashSet};
use futures::channel::oneshot; use futures::channel::oneshot;
use parking_lot::Mutex; use parking_lot::Mutex;
@ -37,12 +38,13 @@ pub(crate) struct LinuxPlatform {
atoms: XcbAtoms, atoms: XcbAtoms,
background_executor: BackgroundExecutor, background_executor: BackgroundExecutor,
foreground_executor: ForegroundExecutor, foreground_executor: ForegroundExecutor,
dispatcher: Arc<LinuxDispatcher>, main_receiver: flume::Receiver<Runnable>,
text_system: Arc<LinuxTextSystem>, text_system: Arc<LinuxTextSystem>,
state: Mutex<LinuxPlatformState>, state: Mutex<LinuxPlatformState>,
} }
pub(crate) struct LinuxPlatformState { pub(crate) struct LinuxPlatformState {
quit_requested: bool,
windows: HashMap<x::Window, Arc<LinuxWindowState>>, windows: HashMap<x::Window, Arc<LinuxWindowState>>,
} }
@ -57,17 +59,24 @@ impl LinuxPlatform {
let (xcb_connection, x_root_index) = xcb::Connection::connect(None).unwrap(); let (xcb_connection, x_root_index) = xcb::Connection::connect(None).unwrap();
let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap(); let atoms = XcbAtoms::intern_all(&xcb_connection).unwrap();
let dispatcher = Arc::new(LinuxDispatcher::new()); let xcb_connection = Arc::new(xcb_connection);
let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
let dispatcher = Arc::new(LinuxDispatcher::new(
main_sender,
&xcb_connection,
x_root_index,
));
Self { Self {
xcb_connection: Arc::new(xcb_connection), xcb_connection,
x_root_index, x_root_index,
atoms, atoms,
background_executor: BackgroundExecutor::new(dispatcher.clone()), background_executor: BackgroundExecutor::new(dispatcher.clone()),
foreground_executor: ForegroundExecutor::new(dispatcher.clone()), foreground_executor: ForegroundExecutor::new(dispatcher.clone()),
dispatcher, main_receiver,
text_system: Arc::new(LinuxTextSystem::new()), text_system: Arc::new(LinuxTextSystem::new()),
state: Mutex::new(LinuxPlatformState { state: Mutex::new(LinuxPlatformState {
quit_requested: false,
windows: HashMap::default(), windows: HashMap::default(),
}), }),
} }
@ -92,8 +101,7 @@ impl Platform for LinuxPlatform {
//Note: here and below, don't keep the lock() open when calling //Note: here and below, don't keep the lock() open when calling
// into window functions as they may invoke callbacks that need // into window functions as they may invoke callbacks that need
// to immediately access the platform (self). // to immediately access the platform (self).
while !self.state.lock().quit_requested {
while !self.state.lock().windows.is_empty() {
let event = self.xcb_connection.wait_for_event().unwrap(); let event = self.xcb_connection.wait_for_event().unwrap();
match event { match event {
xcb::Event::X(x::Event::ClientMessage(ev)) => { xcb::Event::X(x::Event::ClientMessage(ev)) => {
@ -129,15 +137,18 @@ impl Platform for LinuxPlatform {
}; };
window.configure(bounds) window.configure(bounds)
} }
ref other => { _ => {}
println!("Other event {:?}", other); }
}
if let Ok(runnable) = self.main_receiver.try_recv() {
runnable.run();
} }
self.dispatcher.tick_main();
} }
} }
fn quit(&self) {} fn quit(&self) {
self.state.lock().quit_requested = true;
}
fn restart(&self) {} fn restart(&self) {}
@ -186,6 +197,7 @@ impl Platform for LinuxPlatform {
x_window, x_window,
&self.atoms, &self.atoms,
)); ));
self.state self.state
.lock() .lock()
.windows .windows

View file

@ -76,7 +76,7 @@ impl PlatformTextSystem for LinuxTextSystem {
unimplemented!() unimplemented!()
} }
fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> { fn glyph_for_char(&self, font_id: FontId, ch: char) -> Option<GlyphId> {
unimplemented!() None
} }
fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> { fn glyph_raster_bounds(&self, params: &RenderGlyphParams) -> Result<Bounds<DevicePixels>> {
unimplemented!() unimplemented!()

View file

@ -237,6 +237,7 @@ impl LinuxWindowState {
if let Some(fun) = self.callbacks.lock().close.take() { if let Some(fun) = self.callbacks.lock().close.take() {
fun(); fun();
} }
self.xcb_connection.flush().unwrap();
} }
pub fn expose(&self) { pub fn expose(&self) {