Desktop: Switch to the latest unreleased version of Winit (#3177)

* Use unstable winit

* Improve

* Remove unnecessary heap indirection
This commit is contained in:
Timon
2025-09-17 16:40:43 +02:00
committed by Dennis Kobert
parent 4662cbbbc1
commit 3fe7c477e9
14 changed files with 567 additions and 527 deletions
+125 -121
View File
@@ -1,39 +1,37 @@
use crate::CustomEvent;
use crate::cef::WindowSize;
use crate::consts::{APP_NAME, CEF_MESSAGE_LOOP_MAX_ITERATIONS};
use crate::persist::PersistentData;
use crate::render::GraphicsState;
use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Platform};
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
use rfd::AsyncFileDialog;
use std::sync::Arc;
use std::sync::mpsc::Receiver;
use std::sync::mpsc::Sender;
use std::sync::mpsc::SyncSender;
use std::thread;
use std::time::Duration;
use std::time::Instant;
use winit::application::ApplicationHandler;
use winit::dpi::PhysicalSize;
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;
use crate::consts::CEF_MESSAGE_LOOP_MAX_ITERATIONS;
use crate::event::{AppEvent, AppEventScheduler};
use crate::native_window;
use crate::persist::PersistentData;
use crate::render::GraphicsState;
use graphite_desktop_wrapper::messages::{DesktopFrontendMessage, DesktopWrapperMessage, Platform};
use graphite_desktop_wrapper::{DesktopWrapper, NodeGraphExecutionResult, WgpuContext, serialize_frontend_messages};
pub(crate) struct WinitApp {
pub(crate) struct App {
cef_context: Box<dyn cef::CefContext>,
window: Option<Arc<Window>>,
window: Option<Arc<dyn Window>>,
native_window: native_window::NativeWindowHandle,
cef_schedule: Option<Instant>,
window_size_sender: Sender<WindowSize>,
cef_window_size_sender: Sender<cef::WindowSize>,
graphics_state: Option<GraphicsState>,
wgpu_context: WgpuContext,
event_loop_proxy: EventLoopProxy<CustomEvent>,
app_event_receiver: Receiver<AppEvent>,
app_event_scheduler: AppEventScheduler,
desktop_wrapper: DesktopWrapper,
last_ui_update: Instant,
avg_frame_time: f32,
@@ -43,14 +41,20 @@ pub(crate) struct WinitApp {
persistent_data: PersistentData,
}
impl WinitApp {
pub(crate) fn new(cef_context: Box<dyn cef::CefContext>, window_size_sender: Sender<WindowSize>, wgpu_context: WgpuContext, event_loop_proxy: EventLoopProxy<CustomEvent>) -> Self {
let rendering_loop_proxy = event_loop_proxy.clone();
impl App {
pub(crate) fn new(
cef_context: Box<dyn cef::CefContext>,
window_size_sender: Sender<cef::WindowSize>,
wgpu_context: WgpuContext,
app_event_receiver: Receiver<AppEvent>,
app_event_scheduler: AppEventScheduler,
) -> Self {
let rendering_app_event_scheduler = app_event_scheduler.clone();
let (start_render_sender, start_render_receiver) = std::sync::mpsc::sync_channel(1);
std::thread::spawn(move || {
loop {
let result = futures::executor::block_on(DesktopWrapper::execute_node_graph());
let _ = rendering_loop_proxy.send_event(CustomEvent::NodeGraphExecutionResult(result));
rendering_app_event_scheduler.schedule(AppEvent::NodeGraphExecutionResult(result));
let _ = start_render_receiver.recv();
}
});
@@ -63,9 +67,10 @@ impl WinitApp {
window: None,
cef_schedule: Some(Instant::now()),
graphics_state: None,
window_size_sender,
cef_window_size_sender: window_size_sender,
wgpu_context,
event_loop_proxy,
app_event_receiver,
app_event_scheduler,
desktop_wrapper: DesktopWrapper::new(),
last_ui_update: Instant::now(),
avg_frame_time: 0.,
@@ -87,7 +92,7 @@ impl WinitApp {
self.send_or_queue_web_message(bytes);
}
DesktopFrontendMessage::OpenFileDialog { title, filters, context } => {
let event_loop_proxy = self.event_loop_proxy.clone();
let app_event_scheduler = self.app_event_scheduler.clone();
let _ = thread::spawn(move || {
let mut dialog = AsyncFileDialog::new().set_title(title);
for filter in filters {
@@ -100,7 +105,7 @@ impl WinitApp {
&& let Ok(content) = std::fs::read(&path)
{
let message = DesktopWrapperMessage::OpenFileDialogResult { path, content, context };
let _ = event_loop_proxy.send_event(CustomEvent::DesktopWrapperMessage(message));
app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
}
});
}
@@ -111,7 +116,7 @@ impl WinitApp {
filters,
context,
} => {
let event_loop_proxy = self.event_loop_proxy.clone();
let app_event_scheduler = self.app_event_scheduler.clone();
let _ = thread::spawn(move || {
let mut dialog = AsyncFileDialog::new().set_title(title).set_file_name(default_filename);
if let Some(folder) = default_folder {
@@ -125,7 +130,7 @@ impl WinitApp {
if let Some(path) = futures::executor::block_on(show_dialog) {
let message = DesktopWrapperMessage::SaveFileDialogResult { path, context };
let _ = event_loop_proxy.send_event(CustomEvent::DesktopWrapperMessage(message));
app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
}
});
}
@@ -145,7 +150,7 @@ impl WinitApp {
if let Some(graphics_state) = &mut self.graphics_state
&& let Some(window) = &self.window
{
let window_size = window.inner_size();
let window_size = window.surface_size();
let viewport_offset_x = x / window_size.width as f32;
let viewport_offset_y = y / window_size.height as f32;
@@ -173,7 +178,7 @@ impl WinitApp {
}
}
DesktopFrontendMessage::CloseWindow => {
let _ = self.event_loop_proxy.send_event(CustomEvent::CloseWindow);
self.app_event_scheduler.schedule(AppEvent::CloseWindow);
}
DesktopFrontendMessage::PersistenceWriteDocument { id, document } => {
self.persistent_data.write_document(id, document);
@@ -252,43 +257,67 @@ impl WinitApp {
self.web_communication_startup_buffer.push(message);
}
}
}
impl ApplicationHandler<CustomEvent> for WinitApp {
fn about_to_wait(&mut self, event_loop: &ActiveEventLoop) {
// Set a timeout in case we miss any cef schedule requests
let timeout = Instant::now() + Duration::from_millis(10);
let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout));
if let Some(schedule) = self.cef_schedule
&& schedule < Instant::now()
{
self.cef_schedule = None;
// Poll cef message loop multiple times to avoid message loop starvation
for _ in 0..CEF_MESSAGE_LOOP_MAX_ITERATIONS {
self.cef_context.work();
fn user_event(&mut self, event_loop: &dyn ActiveEventLoop, event: AppEvent) {
match event {
AppEvent::WebCommunicationInitialized => {
self.web_communication_initialized = true;
for message in self.web_communication_startup_buffer.drain(..) {
self.cef_context.send_web_message(message);
}
}
AppEvent::DesktopWrapperMessage(message) => self.dispatch_desktop_wrapper_message(message),
AppEvent::NodeGraphExecutionResult(result) => match result {
NodeGraphExecutionResult::HasRun(texture) => {
self.dispatch_desktop_wrapper_message(DesktopWrapperMessage::PollNodeGraphEvaluation);
if let Some(texture) = texture
&& let Some(graphics_state) = self.graphics_state.as_mut()
&& let Some(window) = self.window.as_ref()
{
graphics_state.bind_viewport_texture(texture);
window.request_redraw();
}
}
NodeGraphExecutionResult::NotRun => {}
},
AppEvent::UiUpdate(texture) => {
if let Some(graphics_state) = self.graphics_state.as_mut() {
graphics_state.resize(texture.width(), texture.height());
graphics_state.bind_ui_texture(texture);
let elapsed = self.last_ui_update.elapsed().as_secs_f32();
self.last_ui_update = Instant::now();
if elapsed < 0.5 {
self.avg_frame_time = (self.avg_frame_time * 3. + elapsed) / 4.;
}
}
if let Some(window) = &self.window {
window.request_redraw();
}
}
AppEvent::ScheduleBrowserWork(instant) => {
if instant <= Instant::now() {
self.cef_context.work();
} else {
self.cef_schedule = Some(instant);
}
}
AppEvent::CloseWindow => {
// TODO: Implement graceful shutdown
tracing::info!("Exiting main event loop");
event_loop.exit();
}
}
if let Some(window) = &self.window.as_ref() {
window.request_redraw();
}
event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until));
}
}
impl ApplicationHandler for App {
fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) {
let window_attributes = self.native_window.build(event_loop);
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let mut window = Window::default_attributes()
.with_title(APP_NAME)
.with_min_inner_size(winit::dpi::LogicalSize::new(400, 300))
.with_inner_size(winit::dpi::LogicalSize::new(1200, 800))
.with_resizable(true);
let window: Arc<dyn Window> = Arc::from(event_loop.create_window(window_attributes).unwrap());
window = self.native_window.build(window, event_loop);
self.native_window.setup(window.as_ref());
let window = event_loop.create_window(window).unwrap();
self.native_window.setup(&window);
let window = Arc::new(window);
let graphics_state = GraphicsState::new(window.clone(), self.wgpu_context.clone());
self.window = Some(window);
@@ -307,67 +336,21 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
self.dispatch_desktop_wrapper_message(DesktopWrapperMessage::UpdatePlatform(platform));
}
fn user_event(&mut self, event_loop: &ActiveEventLoop, event: CustomEvent) {
match event {
CustomEvent::WebCommunicationInitialized => {
self.web_communication_initialized = true;
for message in self.web_communication_startup_buffer.drain(..) {
self.cef_context.send_web_message(message);
}
}
CustomEvent::DesktopWrapperMessage(message) => self.dispatch_desktop_wrapper_message(message),
CustomEvent::NodeGraphExecutionResult(result) => match result {
NodeGraphExecutionResult::HasRun(texture) => {
self.dispatch_desktop_wrapper_message(DesktopWrapperMessage::PollNodeGraphEvaluation);
if let Some(texture) = texture
&& let Some(graphics_state) = self.graphics_state.as_mut()
&& let Some(window) = self.window.as_ref()
{
graphics_state.bind_viewport_texture(texture);
window.request_redraw();
}
}
NodeGraphExecutionResult::NotRun => {}
},
CustomEvent::UiUpdate(texture) => {
if let Some(graphics_state) = self.graphics_state.as_mut() {
graphics_state.resize(texture.width(), texture.height());
graphics_state.bind_ui_texture(texture);
let elapsed = self.last_ui_update.elapsed().as_secs_f32();
self.last_ui_update = Instant::now();
if elapsed < 0.5 {
self.avg_frame_time = (self.avg_frame_time * 3. + elapsed) / 4.;
}
}
if let Some(window) = &self.window {
window.request_redraw();
}
}
CustomEvent::ScheduleBrowserWork(instant) => {
if instant <= Instant::now() {
self.cef_context.work();
} else {
self.cef_schedule = Some(instant);
}
}
CustomEvent::CloseWindow => {
// TODO: Implement graceful shutdown
tracing::info!("Exiting main event loop");
event_loop.exit();
}
fn proxy_wake_up(&mut self, event_loop: &dyn ActiveEventLoop) {
while let Ok(event) = self.app_event_receiver.try_recv() {
self.user_event(event_loop, event);
}
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _window_id: WindowId, event: WindowEvent) {
self.cef_context.handle_window_event(&event);
match event {
WindowEvent::CloseRequested => {
let _ = self.event_loop_proxy.send_event(CustomEvent::CloseWindow);
self.app_event_scheduler.schedule(AppEvent::CloseWindow);
}
WindowEvent::Resized(PhysicalSize { width, height }) => {
let _ = self.window_size_sender.send(WindowSize::new(width as usize, height as usize));
WindowEvent::SurfaceResized(size) => {
let _ = self.cef_window_size_sender.send(size.into());
self.cef_context.notify_of_resize();
}
WindowEvent::RedrawRequested => {
@@ -387,18 +370,19 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
let _ = self.start_render_sender.try_send(());
}
}
// Currently not supported on wayland see https://github.com/rust-windowing/winit/issues/1881
WindowEvent::DroppedFile(path) => {
match std::fs::read(&path) {
Ok(content) => {
let message = DesktopWrapperMessage::OpenFile { path, content };
let _ = self.event_loop_proxy.send_event(CustomEvent::DesktopWrapperMessage(message));
}
Err(e) => {
tracing::error!("Failed to read dropped file {}: {}", path.display(), e);
return;
}
};
WindowEvent::DragDropped { paths, .. } => {
for path in paths {
match std::fs::read(&path) {
Ok(content) => {
let message = DesktopWrapperMessage::OpenFile { path, content };
self.app_event_scheduler.schedule(AppEvent::DesktopWrapperMessage(message));
}
Err(e) => {
tracing::error!("Failed to read dropped file {}: {}", path.display(), e);
return;
}
};
}
}
_ => {}
}
@@ -406,4 +390,24 @@ impl ApplicationHandler<CustomEvent> for WinitApp {
// Notify cef of possible input events
self.cef_context.work();
}
fn about_to_wait(&mut self, event_loop: &dyn ActiveEventLoop) {
// Set a timeout in case we miss any cef schedule requests
let timeout = Instant::now() + Duration::from_millis(10);
let wait_until = timeout.min(self.cef_schedule.unwrap_or(timeout));
if let Some(schedule) = self.cef_schedule
&& schedule < Instant::now()
{
self.cef_schedule = None;
// Poll cef message loop multiple times to avoid message loop starvation
for _ in 0..CEF_MESSAGE_LOOP_MAX_ITERATIONS {
self.cef_context.work();
}
}
if let Some(window) = &self.window.as_ref() {
window.request_redraw();
}
event_loop.set_control_flow(ControlFlow::WaitUntil(wait_until));
}
}