Skip to content

Desktop: Refactor window state to not require locking #2928

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Jul 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 63 additions & 71 deletions desktop/src/app.rs
Original file line number Diff line number Diff line change
@@ -1,41 +1,53 @@
use crate::CustomEvent;
use crate::WindowState;
use crate::WindowStateHandle;
use crate::FrameBuffer;
use crate::WindowSize;
use crate::render::GraphicsState;
use std::sync::Arc;
use std::sync::mpsc::Sender;
use std::time::Duration;
use std::time::Instant;
use winit::application::ApplicationHandler;
use winit::dpi::PhysicalSize;
use winit::event::StartCause;
use winit::event::WindowEvent;
use winit::event_loop::ActiveEventLoop;
use winit::event_loop::ControlFlow;
use winit::event_loop::EventLoopProxy;
use winit::window::Window;
use winit::window::WindowId;

use crate::cef;

pub(crate) struct WinitApp {
pub(crate) window_state: WindowStateHandle,
pub(crate) cef_context: cef::Context<cef::Initialized>,
pub(crate) window: Option<Arc<Window>>,
cef_schedule: Option<Instant>,
ui_frame_buffer: Option<FrameBuffer>,
window_size_sender: Sender<WindowSize>,
_viewport_frame_buffer: Option<FrameBuffer>,
graphics_state: Option<GraphicsState>,
event_loop_proxy: EventLoopProxy<CustomEvent>,
}

impl WinitApp {
pub(crate) fn new(window_state: WindowStateHandle, cef_context: cef::Context<cef::Initialized>) -> Self {
pub(crate) fn new(cef_context: cef::Context<cef::Initialized>, window_size_sender: Sender<WindowSize>, event_loop_proxy: EventLoopProxy<CustomEvent>) -> Self {
Self {
window_state,
cef_context,
window: None,
cef_schedule: Some(Instant::now()),
_viewport_frame_buffer: None,
ui_frame_buffer: None,
graphics_state: None,
window_size_sender,
event_loop_proxy,
}
}
}

impl ApplicationHandler<CustomEvent> for WinitApp {
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
let timeout = Instant::now() + Duration::from_millis(10);
// Set a timeout in case we miss any cef schedule requests
let timeout = Instant::now() + Duration::from_millis(100);
let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout));
event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until));
}
Expand All @@ -50,37 +62,42 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
}

fn resumed(&mut self, event_loop: &ActiveEventLoop) {
self.window_state
.with(|s| {
if let WindowState { width: Some(w), height: Some(h), .. } = s {
let window = Arc::new(
event_loop
.create_window(
Window::default_attributes()
.with_title("CEF Offscreen Rendering")
.with_inner_size(winit::dpi::LogicalSize::new(*w as u32, *h as u32)),
)
.unwrap(),
);
let graphics_state = pollster::block_on(GraphicsState::new(window.clone()));
let window = Arc::new(
event_loop
.create_window(
Window::default_attributes()
.with_title("CEF Offscreen Rendering")
.with_inner_size(winit::dpi::LogicalSize::new(1200, 800)),
)
.unwrap(),
);
let graphics_state = pollster::block_on(GraphicsState::new(window.clone()));

self.window = Some(window.clone());
s.graphics_state = Some(graphics_state);
self.window = Some(window);
self.graphics_state = Some(graphics_state);

tracing::info!("Winit window created and ready");
}
})
.unwrap();
tracing::info!("Winit window created and ready");
}

fn user_event(&mut self, _: &ActiveEventLoop, event: CustomEvent) {
match event {
CustomEvent::UiUpdate => {
CustomEvent::UiUpdate(frame_buffer) => {
if let Some(graphics_state) = self.graphics_state.as_mut() {
graphics_state.update_texture(&frame_buffer);
}
self.ui_frame_buffer = Some(frame_buffer);
if let Some(window) = &self.window {
window.request_redraw();
}
}
CustomEvent::ScheduleBrowserWork(instant) => {
if let Some(graphics_state) = self.graphics_state.as_mut()
&& let Some(frame_buffer) = &self.ui_frame_buffer
&& graphics_state.ui_texture_outdated(frame_buffer)
{
self.cef_context.work();
let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(Instant::now() + Duration::from_millis(1)));
}
self.cef_schedule = Some(instant);
}
}
Expand All @@ -94,58 +111,33 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
tracing::info!("The close button was pressed; stopping");
event_loop.exit();
}
WindowEvent::Resized(physical_size) => {
self.window_state
.with(|s| {
let width = physical_size.width as usize;
let height = physical_size.height as usize;
s.width = Some(width);
s.height = Some(height);
if let Some(graphics_state) = &mut s.graphics_state {
graphics_state.resize(width, height);
}
})
.unwrap();
WindowEvent::Resized(PhysicalSize { width, height }) => {
let _ = self.window_size_sender.send(WindowSize::new(width as usize, height as usize));
if let Some(ref mut graphics_state) = self.graphics_state {
graphics_state.resize(width, height);
}
self.cef_context.notify_of_resize();
}

WindowEvent::RedrawRequested => {
self.cef_context.work();
let Some(ref mut graphics_state) = self.graphics_state else { return };
// Only rerender once we have a new ui texture to display

self.window_state
.with(|s| {
if let WindowState {
width: Some(width),
height: Some(height),
graphics_state: Some(graphics_state),
ui_frame_buffer: ui_fb,
..
} = s
{
if let Some(fb) = &*ui_fb {
graphics_state.update_texture(fb);
if fb.width() != *width && fb.height() != *height {
graphics_state.resize(*width, *height);
}
} else if let Some(window) = &self.window {
window.request_redraw();
}

match graphics_state.render() {
Ok(_) => {}
Err(wgpu::SurfaceError::Lost) => {
graphics_state.resize(*width, *height);
}
Err(wgpu::SurfaceError::OutOfMemory) => {
event_loop.exit();
}
Err(e) => tracing::error!("{:?}", e),
}
}
})
.unwrap();
match graphics_state.render() {
Ok(_) => {}
Err(wgpu::SurfaceError::Lost) => {
tracing::warn!("lost surface");
}
Err(wgpu::SurfaceError::OutOfMemory) => {
event_loop.exit();
}
Err(e) => tracing::error!("{:?}", e),
}
}
_ => {}
}

// Notify cef of possible input events
self.cef_context.work();
}
}
59 changes: 55 additions & 4 deletions desktop/src/cef.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use crate::FrameBuffer;
use std::time::Instant;
use crate::{CustomEvent, FrameBuffer};
use std::{
sync::{Arc, Mutex, mpsc::Receiver},
time::Instant,
};

mod context;
mod dirs;
Expand All @@ -8,16 +11,17 @@ mod internal;
mod scheme_handler;

pub(crate) use context::{Context, InitError, Initialized, Setup, SetupError};
use winit::event_loop::EventLoopProxy;

pub(crate) trait CefEventHandler: Clone {
fn window_size(&self) -> WindowSize;
fn draw(&self, frame_buffer: FrameBuffer) -> bool;
fn draw(&self, frame_buffer: FrameBuffer);
/// Scheudule the main event loop to run the cef event loop after the timeout
/// [`_cef_browser_process_handler_t::on_schedule_message_pump_work`] for more documentation.
fn schedule_cef_message_loop_work(&self, scheduled_time: Instant);
}

#[derive(Clone)]
#[derive(Clone, Copy)]
pub(crate) struct WindowSize {
pub(crate) width: usize,
pub(crate) height: usize,
Expand All @@ -28,3 +32,50 @@ impl WindowSize {
Self { width, height }
}
}

#[derive(Clone)]
pub(crate) struct CefHandler {
window_size_receiver: Arc<Mutex<WindowSizeReceiver>>,
event_loop_proxy: EventLoopProxy<CustomEvent>,
}
struct WindowSizeReceiver {
receiver: Receiver<WindowSize>,
window_size: WindowSize,
}
impl WindowSizeReceiver {
fn new(window_size_receiver: Receiver<WindowSize>) -> Self {
Self {
window_size: WindowSize { width: 1, height: 1 },
receiver: window_size_receiver,
}
}
}
impl CefHandler {
pub(crate) fn new(window_size_receiver: Receiver<WindowSize>, event_loop_proxy: EventLoopProxy<CustomEvent>) -> Self {
Self {
window_size_receiver: Arc::new(Mutex::new(WindowSizeReceiver::new(window_size_receiver))),
event_loop_proxy,
}
}
}

impl CefEventHandler for CefHandler {
fn window_size(&self) -> WindowSize {
let Ok(mut guard) = self.window_size_receiver.lock() else {
tracing::error!("Failed to lock window_size_receiver");
return WindowSize::new(1, 1);
};
let WindowSizeReceiver { receiver, window_size } = &mut *guard;
for new_window_size in receiver.try_iter() {
*window_size = new_window_size;
}
*window_size
}
fn draw(&self, frame_buffer: FrameBuffer) {
let _ = self.event_loop_proxy.send_event(CustomEvent::UiUpdate(frame_buffer));
}

fn schedule_cef_message_loop_work(&self, scheduled_time: std::time::Instant) {
let _ = self.event_loop_proxy.send_event(CustomEvent::ScheduleBrowserWork(scheduled_time));
}
}
8 changes: 4 additions & 4 deletions desktop/src/cef/internal/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ pub(crate) struct AppImpl<H: CefEventHandler> {
object: *mut RcImpl<_cef_app_t, Self>,
event_handler: H,
}
impl<H: CefEventHandler> AppImpl<H> {
impl<H: CefEventHandler + Clone> AppImpl<H> {
pub(crate) fn new(event_handler: H) -> Self {
Self {
object: std::ptr::null_mut(),
Expand All @@ -20,7 +20,7 @@ impl<H: CefEventHandler> AppImpl<H> {
}
}

impl<H: CefEventHandler> ImplApp for AppImpl<H> {
impl<H: CefEventHandler + Clone> ImplApp for AppImpl<H> {
fn browser_process_handler(&self) -> Option<BrowserProcessHandler> {
Some(BrowserProcessHandler::new(BrowserProcessHandlerImpl::new(self.event_handler.clone())))
}
Expand All @@ -34,7 +34,7 @@ impl<H: CefEventHandler> ImplApp for AppImpl<H> {
}
}

impl<H: CefEventHandler> Clone for AppImpl<H> {
impl<H: CefEventHandler + Clone> Clone for AppImpl<H> {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
Expand All @@ -54,7 +54,7 @@ impl<H: CefEventHandler> Rc for AppImpl<H> {
}
}
}
impl<H: CefEventHandler> WrapApp for AppImpl<H> {
impl<H: CefEventHandler + Clone> WrapApp for AppImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_app_t, Self>) {
self.object = object;
}
Expand Down
6 changes: 3 additions & 3 deletions desktop/src/cef/internal/browser_process_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ impl<H: CefEventHandler> BrowserProcessHandlerImpl<H> {
}
}

impl<H: CefEventHandler> ImplBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
impl<H: CefEventHandler + Clone> ImplBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
fn on_context_initialized(&self) {
cef::register_scheme_handler_factory(Some(&CefString::from(GRAPHITE_SCHEME)), None, Some(&mut SchemeHandlerFactory::new(GraphiteSchemeHandlerFactory::new())));
}
Expand All @@ -34,7 +34,7 @@ impl<H: CefEventHandler> ImplBrowserProcessHandler for BrowserProcessHandlerImpl
}
}

impl<H: CefEventHandler> Clone for BrowserProcessHandlerImpl<H> {
impl<H: CefEventHandler + Clone> Clone for BrowserProcessHandlerImpl<H> {
fn clone(&self) -> Self {
unsafe {
let rc_impl = &mut *self.object;
Expand All @@ -54,7 +54,7 @@ impl<H: CefEventHandler> Rc for BrowserProcessHandlerImpl<H> {
}
}
}
impl<H: CefEventHandler> WrapBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
impl<H: CefEventHandler + Clone> WrapBrowserProcessHandler for BrowserProcessHandlerImpl<H> {
fn wrap_rc(&mut self, object: *mut RcImpl<_cef_browser_process_handler_t, Self>) {
self.object = object;
}
Expand Down
11 changes: 3 additions & 8 deletions desktop/src/cef/internal/render_handler.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_render_handler_t, cef_base_ref_counted_t};
use cef::{Browser, ImplBrowser, ImplBrowserHost, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler};
use cef::{Browser, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler};

use crate::FrameBuffer;
use crate::cef::CefEventHandler;
Expand Down Expand Up @@ -32,7 +32,7 @@ impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {

fn on_paint(
&self,
browser: Option<&mut Browser>,
_browser: Option<&mut Browser>,
_type_: PaintElementType,
_dirty_rect_count: usize,
_dirty_rects: Option<&Rect>,
Expand All @@ -44,12 +44,7 @@ impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
let buffer_slice = unsafe { std::slice::from_raw_parts(buffer, buffer_size) };
let frame_buffer = FrameBuffer::new(buffer_slice.to_vec(), width as usize, height as usize).expect("Failed to create frame buffer");

let draw_successful = self.event_handler.draw(frame_buffer);
if !draw_successful {
if let Some(browser) = browser {
browser.host().unwrap().was_resized();
}
}
self.event_handler.draw(frame_buffer)
}

fn get_raw(&self) -> *mut _cef_render_handler_t {
Expand Down
Loading
Loading