Skip to content

Commit e4dd3ce

Browse files
Desktop: Use multithreaded CEF event loop on Windows and Linux (#3076)
* Prototype multi threaded event loop * Fix input event dispatch * Remove dead code * Reenable do_message_loop_work for macos targets * Cleanup * Review cleanup * Remove outdated comment * Attempt to fix texture import errors --------- Co-authored-by: Timon Schelling <me@timon.zip>
1 parent 0e46790 commit e4dd3ce

16 files changed

Lines changed: 526 additions & 342 deletions

desktop/src/app.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use winit::window::WindowId;
2424
use crate::cef;
2525

2626
pub(crate) struct WinitApp {
27-
cef_context: cef::Context<cef::Initialized>,
27+
cef_context: Box<dyn cef::CefContext>,
2828
window: Option<Arc<Window>>,
2929
cef_schedule: Option<Instant>,
3030
window_size_sender: Sender<WindowSize>,
@@ -38,7 +38,7 @@ pub(crate) struct WinitApp {
3838
}
3939

4040
impl WinitApp {
41-
pub(crate) fn new(cef_context: cef::Context<cef::Initialized>, window_size_sender: Sender<WindowSize>, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy<CustomEvent>) -> Self {
41+
pub(crate) fn new(cef_context: Box<dyn cef::CefContext>, window_size_sender: Sender<WindowSize>, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy<CustomEvent>) -> Self {
4242
let rendering_loop_proxy = event_loop_proxy.clone();
4343
let (start_render_sender, start_render_receiver) = std::sync::mpsc::sync_channel(1);
4444
std::thread::spawn(move || {
@@ -71,7 +71,7 @@ impl WinitApp {
7171
tracing::error!("Failed to serialize frontend messages");
7272
return;
7373
};
74-
self.cef_context.send_web_message(bytes.as_slice());
74+
self.cef_context.send_web_message(bytes);
7575
}
7676
DesktopFrontendMessage::OpenFileDialog { title, filters, context } => {
7777
let event_loop_proxy = self.event_loop_proxy.clone();
@@ -254,7 +254,7 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
254254
}
255255

256256
fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
257-
let Some(event) = self.cef_context.handle_window_event(event) else { return };
257+
self.cef_context.handle_window_event(&event);
258258

259259
match event {
260260
WindowEvent::CloseRequested => {

desktop/src/cef.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use std::sync::mpsc::Receiver;
2020
use std::sync::{Arc, Mutex};
2121
use std::time::Instant;
2222

23+
mod consts;
2324
mod context;
2425
mod dirs;
2526
mod input;
@@ -34,7 +35,7 @@ mod texture_import;
3435
#[cfg(feature = "accelerated_paint")]
3536
use texture_import::SharedTextureHandle;
3637

37-
pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError};
38+
pub(crate) use context::{CefContext, CefContextBuilder, InitError};
3839
use winit::event_loop::EventLoopProxy;
3940

4041
pub(crate) trait CefEventHandler: Clone {

desktop/src/cef/consts.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub(crate) const GRAPHITE_SCHEME: &str = "graphite-static";
2+
pub(crate) const FRONTEND_DOMAIN: &str = "frontend";

desktop/src/cef/context.rs

Lines changed: 9 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -1,164 +1,15 @@
1-
use cef::sys::{CEF_API_VERSION_LAST, cef_resultcode_t};
2-
use cef::{App, BrowserSettings, Client, DictionaryValue, ImplBrowser, ImplBrowserHost, ImplCommandLine, RenderHandler, RequestContext, WindowInfo, browser_host_create_browser_sync, initialize};
3-
use cef::{Browser, CefString, Settings, api_hash, args::Args, execute_process};
4-
use thiserror::Error;
5-
use winit::event::WindowEvent;
1+
mod multithreaded;
2+
mod singlethreaded;
63

7-
use crate::cef::dirs::{cef_cache_dir, cef_data_dir};
4+
mod builder;
5+
pub(crate) use builder::{CefContextBuilder, InitError};
86

9-
use super::input::InputState;
10-
use super::ipc::{MessageType, SendMessage};
11-
use super::scheme_handler::{FRONTEND_DOMAIN, GRAPHITE_SCHEME};
12-
use super::{CefEventHandler, input};
7+
pub(crate) trait CefContext {
8+
fn work(&mut self);
139

14-
use super::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderHandlerImpl, RenderProcessAppImpl};
10+
fn handle_window_event(&mut self, event: &winit::event::WindowEvent);
1511

16-
pub(crate) struct Setup {}
17-
pub(crate) struct Initialized {}
18-
pub(crate) trait ContextState {}
19-
impl ContextState for Setup {}
20-
impl ContextState for Initialized {}
12+
fn notify_of_resize(&self);
2113

22-
pub(crate) struct Context<S: ContextState> {
23-
args: Args,
24-
pub(crate) browser: Option<Browser>,
25-
pub(crate) input_state: InputState,
26-
marker: std::marker::PhantomData<S>,
27-
}
28-
29-
impl Context<Setup> {
30-
pub(crate) fn new() -> Result<Context<Setup>, SetupError> {
31-
#[cfg(target_os = "macos")]
32-
let _loader = {
33-
let loader = library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
34-
assert!(loader.load());
35-
loader
36-
};
37-
let _ = api_hash(CEF_API_VERSION_LAST, 0);
38-
39-
let args = Args::new();
40-
let cmd = args.as_cmd_line().unwrap();
41-
let switch = CefString::from("type");
42-
let is_browser_process = cmd.has_switch(Some(&switch)) != 1;
43-
44-
if !is_browser_process {
45-
let process_type = CefString::from(&cmd.switch_value(Some(&switch)));
46-
let mut app = RenderProcessAppImpl::app();
47-
let ret = execute_process(Some(args.as_main_args()), Some(&mut app), std::ptr::null_mut());
48-
if ret >= 0 {
49-
return Err(SetupError::SubprocessFailed(process_type.to_string()));
50-
} else {
51-
return Err(SetupError::Subprocess);
52-
}
53-
}
54-
55-
Ok(Context {
56-
args,
57-
browser: None,
58-
input_state: InputState::default(),
59-
marker: std::marker::PhantomData::<Setup>,
60-
})
61-
}
62-
63-
pub(crate) fn init(self, event_handler: impl CefEventHandler) -> Result<Context<Initialized>, InitError> {
64-
let settings = Settings {
65-
windowless_rendering_enabled: 1,
66-
multi_threaded_message_loop: 0,
67-
external_message_pump: 1,
68-
root_cache_path: cef_data_dir().to_str().map(CefString::from).unwrap(),
69-
cache_path: cef_cache_dir().to_str().map(CefString::from).unwrap(),
70-
..Default::default()
71-
};
72-
73-
// Attention! Wrapping this in an extra App is necessary, otherwise the program still compiles but segfaults
74-
let mut cef_app = App::new(BrowserProcessAppImpl::new(event_handler.clone()));
75-
76-
let result = initialize(Some(self.args.as_main_args()), Some(&settings), Some(&mut cef_app), std::ptr::null_mut());
77-
if result != 1 {
78-
let cef_exit_code = cef::get_exit_code() as u32;
79-
if cef_exit_code == cef_resultcode_t::CEF_RESULT_CODE_NORMAL_EXIT_PROCESS_NOTIFIED as u32 {
80-
return Err(InitError::AlreadyRunning);
81-
}
82-
return Err(InitError::InitializationFailed(cef_exit_code));
83-
}
84-
85-
let render_handler = RenderHandler::new(RenderHandlerImpl::new(event_handler.clone()));
86-
let mut client = Client::new(BrowserProcessClientImpl::new(render_handler, event_handler.clone()));
87-
88-
let url = CefString::from(format!("{GRAPHITE_SCHEME}://{FRONTEND_DOMAIN}/").as_str());
89-
// let url = CefString::from("chrome://gpu");
90-
91-
let window_info = WindowInfo {
92-
windowless_rendering_enabled: 1,
93-
#[cfg(feature = "accelerated_paint")]
94-
shared_texture_enabled: if crate::cef::platform::should_enable_hardware_acceleration() { 1 } else { 0 },
95-
..Default::default()
96-
};
97-
98-
let settings = BrowserSettings {
99-
windowless_frame_rate: crate::consts::CEF_WINDOWLESS_FRAME_RATE,
100-
background_color: 0x0,
101-
..Default::default()
102-
};
103-
104-
let browser = browser_host_create_browser_sync(
105-
Some(&window_info),
106-
Some(&mut client),
107-
Some(&url),
108-
Some(&settings),
109-
Option::<&mut DictionaryValue>::None,
110-
Option::<&mut RequestContext>::None,
111-
);
112-
113-
Ok(Context {
114-
args: self.args.clone(),
115-
browser,
116-
input_state: self.input_state.clone(),
117-
marker: std::marker::PhantomData::<Initialized>,
118-
})
119-
}
120-
}
121-
122-
impl Context<Initialized> {
123-
pub(crate) fn work(&mut self) {
124-
cef::do_message_loop_work();
125-
}
126-
127-
pub(crate) fn handle_window_event(&mut self, event: WindowEvent) -> Option<WindowEvent> {
128-
input::handle_window_event(self, event)
129-
}
130-
131-
pub(crate) fn notify_of_resize(&self) {
132-
if let Some(browser) = &self.browser {
133-
browser.host().unwrap().was_resized();
134-
}
135-
}
136-
137-
pub(crate) fn send_web_message(&self, message: &[u8]) {
138-
self.send_message(MessageType::SendToJS, message);
139-
}
140-
}
141-
142-
impl<S: ContextState> Drop for Context<S> {
143-
fn drop(&mut self) {
144-
if self.browser.is_some() {
145-
cef::shutdown();
146-
}
147-
}
148-
}
149-
150-
#[derive(Error, Debug)]
151-
pub(crate) enum SetupError {
152-
#[error("this is the sub process should exit immediately")]
153-
Subprocess,
154-
#[error("subprocess returned non zero exit code")]
155-
SubprocessFailed(String),
156-
}
157-
158-
#[derive(Error, Debug)]
159-
pub(crate) enum InitError {
160-
#[error("initialization failed")]
161-
InitializationFailed(u32),
162-
#[error("Another instance is already running")]
163-
AlreadyRunning,
14+
fn send_web_message(&self, message: Vec<u8>);
16415
}

0 commit comments

Comments
 (0)