Fix message loop handling and improve BrowserProcessHandler (#15)
* add: implement MessagePumpChecker for message loop management * update: refactor message loop handling and improve MessagePumpChecker * update: refactor message loop handling to use MessageLoopTimer and improve BrowserProcessHandler * update: reorganize imports and enhance browser process handler integration * update: support Bevy 0.18 and improve message loop handling * update: remove unused FpsOverlayPlugin and clean up dependencies * update: simplify cef and cef-dll-sys version specifications in Cargo.toml --------- Co-authored-by: not-elm <elmgameinfo@gmail.com>
This commit is contained in:
11
CHANGELOG.md
11
CHANGELOG.md
@@ -2,7 +2,12 @@
|
||||
|
||||
### Features
|
||||
|
||||
- Support Bevy 0.17
|
||||
- Support Bevy 0.18
|
||||
- Enable message loop support for Windows and Linux
|
||||
- Rename `bevy_picking` to `picking`
|
||||
- Change `AmbientLight` to `GlobalAmbientLight`
|
||||
- Update CEF version to 144.2.0+144.0.11
|
||||
- Improve message loop handling
|
||||
- Added `PreloadScripts` component for specifying JavaScript to be executed when the page is initialized.
|
||||
- Enables GPU when debugging.
|
||||
|
||||
@@ -13,6 +18,10 @@
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Bevy 0.18 upgrade introduces breaking changes for users on Bevy 0.17
|
||||
- `bevy_picking` renamed to `picking`
|
||||
- `AmbientLight` changed to `GlobalAmbientLight`
|
||||
- Demo example removed from workspace
|
||||
- Changed `JsEmitEventPlugin` to use `Receive<E>` wrapper for events
|
||||
- Events no longer need to implement the `Event` trait, only `DeserializeOwned + Send + Sync + 'static`
|
||||
- Changed `HostEmitEvent` to `EntityEvent` with required `webview` field
|
||||
|
||||
@@ -38,8 +38,8 @@ bevy = { version = "0.18", default-features = false, features = [
|
||||
"picking",
|
||||
] }
|
||||
bevy_remote = "0.18"
|
||||
cef = { version = "144.2.0+144.0.11" }
|
||||
cef-dll-sys = { version = "144.2.0+144.0.11", features = ["sandbox"] }
|
||||
cef = { version = "144.2.0" }
|
||||
cef-dll-sys = { version = "144.2.0", features = ["sandbox"] }
|
||||
bevy_cef = { path = "." , version = "0.2.0-dev" }
|
||||
bevy_cef_core = { path = "crates/bevy_cef_core", version = "0.2.0-dev" }
|
||||
async-channel = { version = "2.5" }
|
||||
|
||||
@@ -5,6 +5,7 @@ mod client_handler;
|
||||
mod context_menu_handler;
|
||||
mod display_handler;
|
||||
mod localhost;
|
||||
mod message_pump;
|
||||
mod renderer_handler;
|
||||
mod request_context_handler;
|
||||
|
||||
@@ -14,5 +15,6 @@ pub use browsers::*;
|
||||
pub use client_handler::*;
|
||||
pub use context_menu_handler::*;
|
||||
pub use localhost::*;
|
||||
pub use message_pump::*;
|
||||
pub use renderer_handler::*;
|
||||
pub use request_context_handler::*;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use crate::browser_process::MessageLoopTimer;
|
||||
use crate::browser_process::browser_process_handler::BrowserProcessHandlerBuilder;
|
||||
use crate::util::{SCHEME_CEF, cef_scheme_flags};
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
@@ -6,19 +7,21 @@ use cef::{
|
||||
SchemeRegistrar, WrapApp,
|
||||
};
|
||||
use cef_dll_sys::{_cef_app_t, cef_base_ref_counted_t};
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`CefApp Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefApp.html)
|
||||
#[derive(Default)]
|
||||
pub struct BrowserProcessAppBuilder {
|
||||
object: *mut RcImpl<_cef_app_t, Self>,
|
||||
message_loop_working_requester: Sender<MessageLoopTimer>,
|
||||
}
|
||||
|
||||
impl BrowserProcessAppBuilder {
|
||||
pub fn build() -> cef::App {
|
||||
pub fn build(message_loop_working_requester: Sender<MessageLoopTimer>) -> cef::App {
|
||||
cef::App::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
message_loop_working_requester,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -30,7 +33,10 @@ impl Clone for BrowserProcessAppBuilder {
|
||||
rc_impl.interface.add_ref();
|
||||
self.object
|
||||
};
|
||||
Self { object }
|
||||
Self {
|
||||
object,
|
||||
message_loop_working_requester: self.message_loop_working_requester.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,16 +64,18 @@ impl ImplApp for BrowserProcessAppBuilder {
|
||||
// command_line.append_switch(Some(&" disable-gpu-shader-disk-cache".into()));
|
||||
}
|
||||
|
||||
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
|
||||
Some(BrowserProcessHandlerBuilder::build())
|
||||
}
|
||||
|
||||
fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
|
||||
if let Some(registrar) = registrar {
|
||||
registrar.add_custom_scheme(Some(&SCHEME_CEF.into()), cef_scheme_flags() as _);
|
||||
}
|
||||
}
|
||||
|
||||
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
|
||||
Some(BrowserProcessHandlerBuilder::build(
|
||||
self.message_loop_working_requester.clone(),
|
||||
))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut _cef_app_t {
|
||||
self.object as *mut cef::sys::_cef_app_t
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
use crate::prelude::MessageLoopTimer;
|
||||
use cef::rc::{Rc, RcImpl};
|
||||
use cef::*;
|
||||
use std::sync::mpsc::Sender;
|
||||
|
||||
/// ## Reference
|
||||
///
|
||||
/// - [`CefBrowserProcessHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefBrowserProcessHandler.html)
|
||||
pub struct BrowserProcessHandlerBuilder {
|
||||
object: *mut RcImpl<cef_dll_sys::cef_browser_process_handler_t, Self>,
|
||||
message_loop_working_requester: Sender<MessageLoopTimer>,
|
||||
}
|
||||
|
||||
impl BrowserProcessHandlerBuilder {
|
||||
pub fn build() -> BrowserProcessHandler {
|
||||
pub fn build(
|
||||
message_loop_working_requester: Sender<MessageLoopTimer>,
|
||||
) -> BrowserProcessHandler {
|
||||
BrowserProcessHandler::new(Self {
|
||||
object: core::ptr::null_mut(),
|
||||
message_loop_working_requester,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -39,7 +45,10 @@ impl Clone for BrowserProcessHandlerBuilder {
|
||||
rc_impl
|
||||
};
|
||||
|
||||
Self { object }
|
||||
Self {
|
||||
object,
|
||||
message_loop_working_requester: self.message_loop_working_requester.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +65,13 @@ impl ImplBrowserProcessHandler for BrowserProcessHandlerBuilder {
|
||||
command_line.append_switch(Some(&"ignore-ssl-errors".into()));
|
||||
command_line.append_switch(Some(&"enable-logging=stderr".into()));
|
||||
}
|
||||
|
||||
fn on_schedule_message_pump_work(&self, delay_ms: i64) {
|
||||
let _ = self
|
||||
.message_loop_working_requester
|
||||
.send(MessageLoopTimer::new(delay_ms));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn get_raw(&self) -> *mut cef_dll_sys::_cef_browser_process_handler_t {
|
||||
self.object.cast()
|
||||
|
||||
39
crates/bevy_cef_core/src/browser_process/message_pump.rs
Normal file
39
crates/bevy_cef_core/src/browser_process/message_pump.rs
Normal file
@@ -0,0 +1,39 @@
|
||||
use bevy::prelude::Deref;
|
||||
use std::sync::mpsc::Receiver;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
/// Maximum delay between message loop iterations (~30fps).
|
||||
/// Following CEF's official cefclient pattern (`kMaxTimerDelay = 1000/30`).
|
||||
const MAX_TIMER_DELAY_MS: u64 = 1000 / 30;
|
||||
|
||||
#[repr(transparent)]
|
||||
#[derive(Debug, Deref)]
|
||||
pub struct MessageLoopWorkingReceiver(pub Receiver<MessageLoopTimer>);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct MessageLoopTimer(Instant);
|
||||
|
||||
impl MessageLoopTimer {
|
||||
pub fn new(delay_ms: i64) -> Self {
|
||||
let fire_time = if delay_ms <= 0 {
|
||||
Instant::now()
|
||||
} else {
|
||||
Instant::now() + Duration::from_millis(delay_ms as u64)
|
||||
};
|
||||
Self(fire_time)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_finished(&self) -> bool {
|
||||
self.0 <= Instant::now()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deref)]
|
||||
pub struct MessageLoopWorkingMaxDelayTimer(MessageLoopTimer);
|
||||
|
||||
impl Default for MessageLoopWorkingMaxDelayTimer {
|
||||
fn default() -> Self {
|
||||
Self(MessageLoopTimer::new(MAX_TIMER_DELAY_MS as i64))
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
mod browser_process;
|
||||
#[cfg(target_os = "macos")]
|
||||
mod debug;
|
||||
|
||||
mod render_process;
|
||||
mod util;
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ impl ImplApp for RenderProcessAppBuilder {
|
||||
}
|
||||
|
||||
impl WrapApp for RenderProcessAppBuilder {
|
||||
#[inline]
|
||||
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
|
||||
self.object = object;
|
||||
}
|
||||
|
||||
@@ -8,83 +8,88 @@ use cef::{Settings, api_hash, execute_process, initialize, shutdown, sys};
|
||||
///
|
||||
/// - Windows and Linux: Support [`multi_threaded_message_loop`](https://cef-builds.spotifycdn.com/docs/106.1/structcef__settings__t.html#a518ac90db93ca5133a888faa876c08e0), so it is used.
|
||||
/// - macOS: Calls [`CefDoMessageLoopWork`](https://cef-builds.spotifycdn.com/docs/106.1/cef__app_8h.html#a830ae43dcdffcf4e719540204cefdb61) every frame.
|
||||
pub struct MessageLoopPlugin {
|
||||
_app: Box<cef::App>,
|
||||
#[cfg(all(target_os = "macos", not(feature = "debug")))]
|
||||
_loader: Box<cef::library_loader::LibraryLoader>,
|
||||
#[cfg(all(target_os = "macos", feature = "debug"))]
|
||||
_loader: Box<DebugLibraryLoader>,
|
||||
}
|
||||
pub struct MessageLoopPlugin;
|
||||
|
||||
impl Plugin for MessageLoopPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
#[cfg(target_os = "macos")]
|
||||
load_cef_library(app);
|
||||
|
||||
let _ = api_hash(sys::CEF_API_VERSION_LAST, 0);
|
||||
let args = Args::new();
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
let mut cef_app = BrowserProcessAppBuilder::build(tx);
|
||||
let ret = execute_process(
|
||||
Some(args.as_main_args()),
|
||||
Some(&mut cef_app),
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
assert_eq!(ret, -1, "cannot execute browser process");
|
||||
|
||||
cef_initialize(&args, &mut cef_app);
|
||||
|
||||
app.insert_non_send_resource(cef_app);
|
||||
app.insert_non_send_resource(MessageLoopWorkingReceiver(rx));
|
||||
app.insert_non_send_resource(RunOnMainThread)
|
||||
.add_systems(Main, cef_do_message_loop_work)
|
||||
.add_systems(Update, cef_shutdown.run_if(on_message::<AppExit>));
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MessageLoopPlugin {
|
||||
fn default() -> Self {
|
||||
#[cfg(target_os = "macos")]
|
||||
let _loader = {
|
||||
macos::install_cef_app_protocol();
|
||||
#[cfg(all(target_os = "macos", feature = "debug"))]
|
||||
let loader = DebugLibraryLoader::new();
|
||||
#[cfg(all(target_os = "macos", not(feature = "debug")))]
|
||||
let loader =
|
||||
cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
|
||||
assert!(loader.load());
|
||||
loader
|
||||
};
|
||||
|
||||
let _ = api_hash(sys::CEF_API_VERSION_LAST, 0);
|
||||
|
||||
let args = Args::new();
|
||||
let mut app = BrowserProcessAppBuilder::build();
|
||||
let ret = execute_process(
|
||||
Some(args.as_main_args()),
|
||||
Some(&mut app),
|
||||
std::ptr::null_mut(),
|
||||
);
|
||||
assert_eq!(ret, -1, "cannot execute browser process");
|
||||
|
||||
let settings = Settings {
|
||||
#[cfg(all(target_os = "macos", feature = "debug"))]
|
||||
framework_dir_path: debug_chromium_embedded_framework_dir_path()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.into(),
|
||||
#[cfg(all(target_os = "macos", feature = "debug"))]
|
||||
browser_subprocess_path: debug_render_process_path().to_str().unwrap().into(),
|
||||
#[cfg(all(target_os = "macos", feature = "debug"))]
|
||||
no_sandbox: true as _,
|
||||
windowless_rendering_enabled: true as _,
|
||||
// #[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
// multi_threaded_message_loop: true as _,
|
||||
#[cfg(target_os = "macos")]
|
||||
external_message_pump: true as _,
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(
|
||||
initialize(
|
||||
Some(args.as_main_args()),
|
||||
Some(&settings),
|
||||
Some(&mut app),
|
||||
std::ptr::null_mut(),
|
||||
),
|
||||
1
|
||||
);
|
||||
Self {
|
||||
_app: Box::new(app),
|
||||
#[cfg(target_os = "macos")]
|
||||
_loader: Box::new(_loader),
|
||||
}
|
||||
}
|
||||
#[cfg(target_os = "macos")]
|
||||
fn load_cef_library(app: &mut App) {
|
||||
macos::install_cef_app_protocol();
|
||||
#[cfg(all(target_os = "macos", feature = "debug"))]
|
||||
let loader = DebugLibraryLoader::new();
|
||||
#[cfg(all(target_os = "macos", not(feature = "debug")))]
|
||||
let loader = cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
|
||||
assert!(loader.load());
|
||||
app.insert_non_send_resource(loader);
|
||||
}
|
||||
|
||||
fn cef_do_message_loop_work(_: NonSend<RunOnMainThread>) {
|
||||
cef::do_message_loop_work();
|
||||
fn cef_initialize(args: &Args, cef_app: &mut cef::App) {
|
||||
let settings = Settings {
|
||||
#[cfg(all(target_os = "macos", feature = "debug"))]
|
||||
framework_dir_path: debug_chromium_embedded_framework_dir_path()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.into(),
|
||||
#[cfg(all(target_os = "macos", feature = "debug"))]
|
||||
browser_subprocess_path: debug_render_process_path().to_str().unwrap().into(),
|
||||
#[cfg(all(target_os = "macos", feature = "debug"))]
|
||||
no_sandbox: true as _,
|
||||
windowless_rendering_enabled: true as _,
|
||||
// #[cfg(any(target_os = "windows", target_os = "linux"))]
|
||||
// multi_threaded_message_loop: true as _,
|
||||
multi_threaded_message_loop: false as _,
|
||||
external_message_pump: true as _,
|
||||
..Default::default()
|
||||
};
|
||||
assert_eq!(
|
||||
initialize(
|
||||
Some(args.as_main_args()),
|
||||
Some(&settings),
|
||||
Some(cef_app),
|
||||
std::ptr::null_mut(),
|
||||
),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
fn cef_do_message_loop_work(
|
||||
receiver: NonSend<MessageLoopWorkingReceiver>,
|
||||
mut timer: Local<Option<MessageLoopTimer>>,
|
||||
mut max_delay_timer: Local<MessageLoopWorkingMaxDelayTimer>,
|
||||
) {
|
||||
while let Ok(t) = receiver.try_recv() {
|
||||
timer.replace(t);
|
||||
}
|
||||
if timer.as_ref().map(|t| t.is_finished()).unwrap_or(false) || max_delay_timer.is_finished() {
|
||||
cef::do_message_loop_work();
|
||||
*max_delay_timer = MessageLoopWorkingMaxDelayTimer::default();
|
||||
timer.take();
|
||||
}
|
||||
}
|
||||
|
||||
fn cef_shutdown(_: NonSend<RunOnMainThread>) {
|
||||
|
||||
@@ -30,7 +30,7 @@ impl Plugin for CefPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_plugins((
|
||||
LocalHostPlugin,
|
||||
MessageLoopPlugin::default(),
|
||||
MessageLoopPlugin,
|
||||
WebviewCoreComponentsPlugin,
|
||||
WebviewPlugin,
|
||||
IpcPlugin,
|
||||
|
||||
Reference in New Issue
Block a user