diff --git a/CHANGELOG.md b/CHANGELOG.md index 625c091..d9bbd47 100644 --- a/CHANGELOG.md +++ b/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` 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 diff --git a/Cargo.toml b/Cargo.toml index 89d43ae..b8c5128 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" } diff --git a/crates/bevy_cef_core/src/browser_process.rs b/crates/bevy_cef_core/src/browser_process.rs index 0628f38..4c4ed69 100644 --- a/crates/bevy_cef_core/src/browser_process.rs +++ b/crates/bevy_cef_core/src/browser_process.rs @@ -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::*; diff --git a/crates/bevy_cef_core/src/browser_process/app.rs b/crates/bevy_cef_core/src/browser_process/app.rs index 33690f4..18a4bba 100644 --- a/crates/bevy_cef_core/src/browser_process/app.rs +++ b/crates/bevy_cef_core/src/browser_process/app.rs @@ -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, } impl BrowserProcessAppBuilder { - pub fn build() -> cef::App { + pub fn build(message_loop_working_requester: Sender) -> 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 { - 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 { + 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 diff --git a/crates/bevy_cef_core/src/browser_process/browser_process_handler.rs b/crates/bevy_cef_core/src/browser_process/browser_process_handler.rs index c48598f..e0fcbfb 100644 --- a/crates/bevy_cef_core/src/browser_process/browser_process_handler.rs +++ b/crates/bevy_cef_core/src/browser_process/browser_process_handler.rs @@ -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, + message_loop_working_requester: Sender, } impl BrowserProcessHandlerBuilder { - pub fn build() -> BrowserProcessHandler { + pub fn build( + message_loop_working_requester: Sender, + ) -> 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() diff --git a/crates/bevy_cef_core/src/browser_process/message_pump.rs b/crates/bevy_cef_core/src/browser_process/message_pump.rs new file mode 100644 index 0000000..e0d45f2 --- /dev/null +++ b/crates/bevy_cef_core/src/browser_process/message_pump.rs @@ -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); + +#[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)) + } +} diff --git a/crates/bevy_cef_core/src/lib.rs b/crates/bevy_cef_core/src/lib.rs index 51bf0be..d32f523 100644 --- a/crates/bevy_cef_core/src/lib.rs +++ b/crates/bevy_cef_core/src/lib.rs @@ -1,6 +1,7 @@ mod browser_process; #[cfg(target_os = "macos")] mod debug; + mod render_process; mod util; diff --git a/crates/bevy_cef_core/src/render_process/app.rs b/crates/bevy_cef_core/src/render_process/app.rs index c4eca8f..b94646f 100644 --- a/crates/bevy_cef_core/src/render_process/app.rs +++ b/crates/bevy_cef_core/src/render_process/app.rs @@ -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; } diff --git a/src/common/message_loop.rs b/src/common/message_loop.rs index 0c23b9e..5301a36 100644 --- a/src/common/message_loop.rs +++ b/src/common/message_loop.rs @@ -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, - #[cfg(all(target_os = "macos", not(feature = "debug")))] - _loader: Box, - #[cfg(all(target_os = "macos", feature = "debug"))] - _loader: Box, -} +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::)); } } -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) { - 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, + mut timer: Local>, + mut max_delay_timer: Local, +) { + 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) { diff --git a/src/lib.rs b/src/lib.rs index 33b693c..7d8cf10 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,7 @@ impl Plugin for CefPlugin { fn build(&self, app: &mut App) { app.add_plugins(( LocalHostPlugin, - MessageLoopPlugin::default(), + MessageLoopPlugin, WebviewCoreComponentsPlugin, WebviewPlugin, IpcPlugin,