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
|
### 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
|
||||||
|
|||||||
@@ -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" }
|
||||||
|
|||||||
@@ -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::*;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
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;
|
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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 {
|
|
||||||
fn default() -> Self {
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
let _loader = {
|
fn load_cef_library(app: &mut App) {
|
||||||
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>) {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user