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:
elm
2026-02-01 12:00:56 +09:00
committed by GitHub
parent 8f38892009
commit 5be6380474
10 changed files with 160 additions and 79 deletions

View File

@@ -2,7 +2,12 @@
### Features ### 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. - Added `PreloadScripts` component for specifying JavaScript to be executed when the page is initialized.
- Enables GPU when debugging. - Enables GPU when debugging.
@@ -13,6 +18,10 @@
### Breaking Changes ### 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 - Changed `JsEmitEventPlugin` to use `Receive<E>` wrapper for events
- Events no longer need to implement the `Event` trait, only `DeserializeOwned + Send + Sync + 'static` - Events no longer need to implement the `Event` trait, only `DeserializeOwned + Send + Sync + 'static`
- Changed `HostEmitEvent` to `EntityEvent` with required `webview` field - Changed `HostEmitEvent` to `EntityEvent` with required `webview` field

View File

@@ -38,8 +38,8 @@ bevy = { version = "0.18", default-features = false, features = [
"picking", "picking",
] } ] }
bevy_remote = "0.18" bevy_remote = "0.18"
cef = { version = "144.2.0+144.0.11" } cef = { version = "144.2.0" }
cef-dll-sys = { version = "144.2.0+144.0.11", features = ["sandbox"] } cef-dll-sys = { version = "144.2.0", features = ["sandbox"] }
bevy_cef = { path = "." , version = "0.2.0-dev" } bevy_cef = { path = "." , version = "0.2.0-dev" }
bevy_cef_core = { path = "crates/bevy_cef_core", version = "0.2.0-dev" } bevy_cef_core = { path = "crates/bevy_cef_core", version = "0.2.0-dev" }
async-channel = { version = "2.5" } async-channel = { version = "2.5" }

View File

@@ -5,6 +5,7 @@ mod client_handler;
mod context_menu_handler; mod context_menu_handler;
mod display_handler; mod display_handler;
mod localhost; mod localhost;
mod message_pump;
mod renderer_handler; mod renderer_handler;
mod request_context_handler; mod request_context_handler;
@@ -14,5 +15,6 @@ pub use browsers::*;
pub use client_handler::*; pub use client_handler::*;
pub use context_menu_handler::*; pub use context_menu_handler::*;
pub use localhost::*; pub use localhost::*;
pub use message_pump::*;
pub use renderer_handler::*; pub use renderer_handler::*;
pub use request_context_handler::*; pub use request_context_handler::*;

View File

@@ -1,3 +1,4 @@
use crate::browser_process::MessageLoopTimer;
use crate::browser_process::browser_process_handler::BrowserProcessHandlerBuilder; use crate::browser_process::browser_process_handler::BrowserProcessHandlerBuilder;
use crate::util::{SCHEME_CEF, cef_scheme_flags}; use crate::util::{SCHEME_CEF, cef_scheme_flags};
use cef::rc::{Rc, RcImpl}; use cef::rc::{Rc, RcImpl};
@@ -6,19 +7,21 @@ use cef::{
SchemeRegistrar, WrapApp, SchemeRegistrar, WrapApp,
}; };
use cef_dll_sys::{_cef_app_t, cef_base_ref_counted_t}; use cef_dll_sys::{_cef_app_t, cef_base_ref_counted_t};
use std::sync::mpsc::Sender;
/// ## Reference /// ## Reference
/// ///
/// - [`CefApp Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefApp.html) /// - [`CefApp Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefApp.html)
#[derive(Default)]
pub struct BrowserProcessAppBuilder { pub struct BrowserProcessAppBuilder {
object: *mut RcImpl<_cef_app_t, Self>, object: *mut RcImpl<_cef_app_t, Self>,
message_loop_working_requester: Sender<MessageLoopTimer>,
} }
impl BrowserProcessAppBuilder { impl BrowserProcessAppBuilder {
pub fn build() -> cef::App { pub fn build(message_loop_working_requester: Sender<MessageLoopTimer>) -> cef::App {
cef::App::new(Self { cef::App::new(Self {
object: core::ptr::null_mut(), object: core::ptr::null_mut(),
message_loop_working_requester,
}) })
} }
} }
@@ -30,7 +33,10 @@ impl Clone for BrowserProcessAppBuilder {
rc_impl.interface.add_ref(); rc_impl.interface.add_ref();
self.object 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())); // 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>) { fn on_register_custom_schemes(&self, registrar: Option<&mut SchemeRegistrar>) {
if let Some(registrar) = registrar { if let Some(registrar) = registrar {
registrar.add_custom_scheme(Some(&SCHEME_CEF.into()), cef_scheme_flags() as _); 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] #[inline]
fn get_raw(&self) -> *mut _cef_app_t { fn get_raw(&self) -> *mut _cef_app_t {
self.object as *mut cef::sys::_cef_app_t self.object as *mut cef::sys::_cef_app_t

View File

@@ -1,17 +1,23 @@
use crate::prelude::MessageLoopTimer;
use cef::rc::{Rc, RcImpl}; use cef::rc::{Rc, RcImpl};
use cef::*; use cef::*;
use std::sync::mpsc::Sender;
/// ## Reference /// ## Reference
/// ///
/// - [`CefBrowserProcessHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefBrowserProcessHandler.html) /// - [`CefBrowserProcessHandler Class Reference`](https://cef-builds.spotifycdn.com/docs/106.1/classCefBrowserProcessHandler.html)
pub struct BrowserProcessHandlerBuilder { pub struct BrowserProcessHandlerBuilder {
object: *mut RcImpl<cef_dll_sys::cef_browser_process_handler_t, Self>, object: *mut RcImpl<cef_dll_sys::cef_browser_process_handler_t, Self>,
message_loop_working_requester: Sender<MessageLoopTimer>,
} }
impl BrowserProcessHandlerBuilder { impl BrowserProcessHandlerBuilder {
pub fn build() -> BrowserProcessHandler { pub fn build(
message_loop_working_requester: Sender<MessageLoopTimer>,
) -> BrowserProcessHandler {
BrowserProcessHandler::new(Self { BrowserProcessHandler::new(Self {
object: core::ptr::null_mut(), object: core::ptr::null_mut(),
message_loop_working_requester,
}) })
} }
} }
@@ -39,7 +45,10 @@ impl Clone for BrowserProcessHandlerBuilder {
rc_impl 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(&"ignore-ssl-errors".into()));
command_line.append_switch(Some(&"enable-logging=stderr".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] #[inline]
fn get_raw(&self) -> *mut cef_dll_sys::_cef_browser_process_handler_t { fn get_raw(&self) -> *mut cef_dll_sys::_cef_browser_process_handler_t {
self.object.cast() self.object.cast()

View 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))
}
}

View File

@@ -1,6 +1,7 @@
mod browser_process; mod browser_process;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod debug; mod debug;
mod render_process; mod render_process;
mod util; mod util;

View File

@@ -57,6 +57,7 @@ impl ImplApp for RenderProcessAppBuilder {
} }
impl WrapApp for RenderProcessAppBuilder { impl WrapApp for RenderProcessAppBuilder {
#[inline]
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) { fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
self.object = object; self.object = object;
} }

View File

@@ -8,47 +8,47 @@ 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. /// - 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. /// - macOS: Calls [`CefDoMessageLoopWork`](https://cef-builds.spotifycdn.com/docs/106.1/cef__app_8h.html#a830ae43dcdffcf4e719540204cefdb61) every frame.
pub struct MessageLoopPlugin { 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>,
}
impl Plugin for MessageLoopPlugin { impl Plugin for MessageLoopPlugin {
fn build(&self, app: &mut App) { 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) app.insert_non_send_resource(RunOnMainThread)
.add_systems(Main, cef_do_message_loop_work) .add_systems(Main, cef_do_message_loop_work)
.add_systems(Update, cef_shutdown.run_if(on_message::<AppExit>)); .add_systems(Update, cef_shutdown.run_if(on_message::<AppExit>));
} }
} }
impl Default for MessageLoopPlugin { #[cfg(target_os = "macos")]
fn default() -> Self { fn load_cef_library(app: &mut App) {
#[cfg(target_os = "macos")]
let _loader = {
macos::install_cef_app_protocol(); macos::install_cef_app_protocol();
#[cfg(all(target_os = "macos", feature = "debug"))] #[cfg(all(target_os = "macos", feature = "debug"))]
let loader = DebugLibraryLoader::new(); let loader = DebugLibraryLoader::new();
#[cfg(all(target_os = "macos", not(feature = "debug")))] #[cfg(all(target_os = "macos", not(feature = "debug")))]
let loader = let loader = cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
cef::library_loader::LibraryLoader::new(&std::env::current_exe().unwrap(), false);
assert!(loader.load()); assert!(loader.load());
loader app.insert_non_send_resource(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");
fn cef_initialize(args: &Args, cef_app: &mut cef::App) {
let settings = Settings { let settings = Settings {
#[cfg(all(target_os = "macos", feature = "debug"))] #[cfg(all(target_os = "macos", feature = "debug"))]
framework_dir_path: debug_chromium_embedded_framework_dir_path() framework_dir_path: debug_chromium_embedded_framework_dir_path()
@@ -62,7 +62,7 @@ impl Default for MessageLoopPlugin {
windowless_rendering_enabled: true as _, windowless_rendering_enabled: true as _,
// #[cfg(any(target_os = "windows", target_os = "linux"))] // #[cfg(any(target_os = "windows", target_os = "linux"))]
// multi_threaded_message_loop: true as _, // multi_threaded_message_loop: true as _,
#[cfg(target_os = "macos")] multi_threaded_message_loop: false as _,
external_message_pump: true as _, external_message_pump: true as _,
..Default::default() ..Default::default()
}; };
@@ -70,21 +70,26 @@ impl Default for MessageLoopPlugin {
initialize( initialize(
Some(args.as_main_args()), Some(args.as_main_args()),
Some(&settings), Some(&settings),
Some(&mut app), Some(cef_app),
std::ptr::null_mut(), std::ptr::null_mut(),
), ),
1 1
); );
Self {
_app: Box::new(app),
#[cfg(target_os = "macos")]
_loader: Box::new(_loader),
}
}
} }
fn cef_do_message_loop_work(_: NonSend<RunOnMainThread>) { 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(); cef::do_message_loop_work();
*max_delay_timer = MessageLoopWorkingMaxDelayTimer::default();
timer.take();
}
} }
fn cef_shutdown(_: NonSend<RunOnMainThread>) { fn cef_shutdown(_: NonSend<RunOnMainThread>) {

View File

@@ -30,7 +30,7 @@ impl Plugin for CefPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_plugins(( app.add_plugins((
LocalHostPlugin, LocalHostPlugin,
MessageLoopPlugin::default(), MessageLoopPlugin,
WebviewCoreComponentsPlugin, WebviewCoreComponentsPlugin,
WebviewPlugin, WebviewPlugin,
IpcPlugin, IpcPlugin,