Skip to content

Commit

Permalink
Prepare winit event loop for show()/hide() on PlatformWindow
Browse files Browse the repository at this point in the history
Reduce the dependency of the GLRenderer to a new trait that exposes the
EventLoopTarget and EventLoopProxy. Those are provided by either an
winit::event_loop::EventLoop or, once the loop is started using the
self-consuming run(), by the event loop target provided to the run_fn.

This way renderers can be created from either within run or before.
The initial event loop instance is kept in TLS. When starting the loop,
it's taken out and instead the event loop target is placed into a scoped
tls variable.
  • Loading branch information
tronical committed Jan 18, 2021
1 parent 88839ea commit 2b7a1ee
Show file tree
Hide file tree
Showing 3 changed files with 117 additions and 63 deletions.
140 changes: 96 additions & 44 deletions sixtyfps_runtime/rendering_backends/gl/eventloop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,67 @@ use std::rc::{Rc, Weak};
#[cfg(not(target_arch = "wasm32"))]
use winit::platform::run_return::EventLoopExtRunReturn;

struct NotRunningEventLoop {
instance: winit::event_loop::EventLoop<CustomEvent>,
event_loop_proxy: winit::event_loop::EventLoopProxy<CustomEvent>,
}

impl NotRunningEventLoop {
fn new() -> Self {
let instance = winit::event_loop::EventLoop::with_user_event();
let event_loop_proxy = instance.create_proxy();
Self { instance, event_loop_proxy }
}
}

struct RunningEventLoop<'a> {
event_loop_target: &'a winit::event_loop::EventLoopWindowTarget<CustomEvent>,
event_loop_proxy: winit::event_loop::EventLoopProxy<CustomEvent>,
}

pub(crate) trait EventLoopInterface {
fn event_loop_target(&self) -> &winit::event_loop::EventLoopWindowTarget<CustomEvent>;
fn event_loop_proxy(&self) -> &winit::event_loop::EventLoopProxy<CustomEvent>;
}

impl EventLoopInterface for NotRunningEventLoop {
fn event_loop_target(&self) -> &winit::event_loop::EventLoopWindowTarget<CustomEvent> {
&*self.instance
}

fn event_loop_proxy(&self) -> &winit::event_loop::EventLoopProxy<CustomEvent> {
&self.event_loop_proxy
}
}

impl<'a> EventLoopInterface for RunningEventLoop<'a> {
fn event_loop_target(&self) -> &winit::event_loop::EventLoopWindowTarget<CustomEvent> {
self.event_loop_target
}

fn event_loop_proxy(&self) -> &winit::event_loop::EventLoopProxy<CustomEvent> {
&self.event_loop_proxy
}
}

thread_local! {
static ALL_WINDOWS: RefCell<std::collections::HashMap<winit::window::WindowId, Weak<crate::graphics_window::GraphicsWindow>>> = RefCell::new(std::collections::HashMap::new());
static MAYBE_LOOP_INSTANCE: RefCell<Option<NotRunningEventLoop>> = RefCell::new(Some(NotRunningEventLoop::new()));
}

scoped_tls_hkt::scoped_thread_local!(static CURRENT_WINDOW_TARGET : for<'a> &'a RunningEventLoop<'a>);

pub(crate) fn with_window_target<T>(callback: impl FnOnce(&dyn EventLoopInterface) -> T) -> T {
if CURRENT_WINDOW_TARGET.is_set() {
CURRENT_WINDOW_TARGET.with(|current_target| callback(current_target))
} else {
MAYBE_LOOP_INSTANCE.with(|loop_instance| {
if loop_instance.borrow().is_none() {
*loop_instance.borrow_mut() = Some(NotRunningEventLoop::new());
}
callback(loop_instance.borrow().as_ref().unwrap())
})
}
}

pub fn register_window(
Expand All @@ -50,35 +109,33 @@ pub fn unregister_window(id: winit::window::WindowId) {
pub enum CustomEvent {
/// Request for the event loop to wake up and poll. This is used on the web for example to
/// request an animation frame.
#[cfg(target_arch = "wasm32")]
WakeUpAndPoll,
}

/// This is the main structure to hold the event loop responsible for delegating events from the
/// windowing system to the individual windows managed by the run-time, and then subsequently to
/// the items. These are typically rendering and input events.
pub struct EventLoop {
winit_loop: winit::event_loop::EventLoop<CustomEvent>,
}
/// Runs the event loop and renders the items in the provided `component` in its
/// own window.
#[allow(unused_mut)] // mut need changes for wasm
pub fn run() {
use winit::event::Event;
use winit::event_loop::{ControlFlow, EventLoopWindowTarget};

impl EventLoop {
/// Returns a new instance of the event loop, backed by a winit eventloop.
pub fn new() -> Self {
Self { winit_loop: winit::event_loop::EventLoop::with_user_event() }
}
let not_running_loop_instance = MAYBE_LOOP_INSTANCE.with(|loop_instance| {
loop_instance.borrow_mut().take().unwrap_or_else(|| NotRunningEventLoop::new())
});

/// Runs the event loop and renders the items in the provided `component` in its
/// own window.
#[allow(unused_mut)] // mut need changes for wasm
pub fn run(mut self) {
use winit::event::Event;
use winit::event_loop::{ControlFlow, EventLoopWindowTarget};
let event_loop_proxy = not_running_loop_instance.event_loop_proxy;
let mut winit_loop = not_running_loop_instance.instance;

// last seen cursor position, (physical coordinate)
let mut cursor_pos = Point::default();
let mut pressed = false;
let mut run_fn = move |event: Event<CustomEvent>,
_: &EventLoopWindowTarget<CustomEvent>,
control_flow: &mut ControlFlow| {
// last seen cursor position, (physical coordinate)
let mut cursor_pos = Point::default();
let mut pressed = false;
let mut run_fn = move |event: Event<CustomEvent>,
event_loop_target: &EventLoopWindowTarget<CustomEvent>,
control_flow: &mut ControlFlow| {
let running_instance =
RunningEventLoop { event_loop_target, event_loop_proxy: event_loop_proxy.clone() };
CURRENT_WINDOW_TARGET.set(&running_instance, || {
*control_flow = ControlFlow::Wait;

match event {
Expand Down Expand Up @@ -350,27 +407,22 @@ impl EventLoop {
*control_flow = winit::event_loop::ControlFlow::WaitUntil(next_timer);
}
}
};

#[cfg(not(target_arch = "wasm32"))]
self.winit_loop.run_return(run_fn);
#[cfg(target_arch = "wasm32")]
{
// Since wasm does not have a run_return function that takes a non-static closure,
// we use this hack to work that around
scoped_tls_hkt::scoped_thread_local!(static mut RUN_FN_TLS: for <'a> &'a mut dyn FnMut(
Event<'_, CustomEvent>,
&EventLoopWindowTarget<CustomEvent>,
&mut ControlFlow,
));
RUN_FN_TLS.set(&mut run_fn, move || {
self.winit_loop.run(|e, t, cf| RUN_FN_TLS.with(|mut run_fn| run_fn(e, t, cf)))
});
}
}
})
};

/// Returns a reference to the backing winit event loop.
pub fn get_winit_event_loop(&self) -> &winit::event_loop::EventLoop<CustomEvent> {
&self.winit_loop
#[cfg(not(target_arch = "wasm32"))]
winit_loop.run_return(run_fn);
#[cfg(target_arch = "wasm32")]
{
// Since wasm does not have a run_return function that takes a non-static closure,
// we use this hack to work that around
scoped_tls_hkt::scoped_thread_local!(static mut RUN_FN_TLS: for <'a> &'a mut dyn FnMut(
Event<'_, CustomEvent>,
&EventLoopWindowTarget<CustomEvent>,
&mut ControlFlow,
));
RUN_FN_TLS.set(&mut run_fn, move || {
winit_loop.run(|e, t, cf| RUN_FN_TLS.with(|run_fn| run_fn(e, t, cf)))
});
}
}
20 changes: 9 additions & 11 deletions sixtyfps_runtime/rendering_backends/gl/graphics_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use sixtyfps_corelib as corelib;
type Backend = super::GLRenderer;

type WindowFactoryFn =
dyn Fn(&crate::eventloop::EventLoop, winit::window::WindowBuilder) -> Backend;
dyn Fn(&dyn crate::eventloop::EventLoopInterface, winit::window::WindowBuilder) -> Backend;

/// GraphicsWindow is an implementation of the [PlatformWindow][`crate::eventloop::PlatformWindow`] trait. This is
/// typically instantiated by entry factory functions of the different graphics backends.
Expand All @@ -54,8 +54,8 @@ impl GraphicsWindow {
/// * `graphics_backend_factory`: The factor function stored in the GraphicsWindow that's called when the state
/// of the window changes to mapped. The event loop and window builder parameters can be used to create a
/// backing window.
pub fn new(
graphics_backend_factory: impl Fn(&crate::eventloop::EventLoop, winit::window::WindowBuilder) -> Backend
pub(crate) fn new(
graphics_backend_factory: impl Fn(&dyn crate::eventloop::EventLoopInterface, winit::window::WindowBuilder) -> Backend
+ 'static,
) -> Rc<Self> {
Rc::new(Self {
Expand Down Expand Up @@ -115,13 +115,11 @@ impl GraphicsWindow {
/// Requests for the window to be mapped to the screen.
///
/// Arguments:
/// * `event_loop`: The event loop used to drive further event handling for this window
/// as it will receive events.
/// * `component`: The component that holds the root item of the scene. If the item is a [`corelib::items::Window`], then
/// the `width` and `height` properties are read and the values are passed to the windowing system as request
/// for the initial size of the window. Then bindings are installed on these properties to keep them up-to-date
/// with the size as it may be changed by the user or the windowing system in general.
fn map_window(self: Rc<Self>, event_loop: &crate::eventloop::EventLoop) {
fn map_window(self: Rc<Self>) {
if matches!(&*self.map_state.borrow(), GraphicsWindowBackendState::Mapped(..)) {
return;
}
Expand All @@ -139,7 +137,9 @@ impl GraphicsWindow {
let window_builder = winit::window::WindowBuilder::new().with_title(window_title);

let id = {
let backend = self.window_factory.as_ref()(&event_loop, window_builder);
let backend = crate::eventloop::with_window_target(|event_loop| {
self.window_factory.as_ref()(event_loop, window_builder)
});

// Ideally we should be passing the initial requested size to the window builder, but those properties
// may be specified in logical pixels, relative to the scale factory, which we only know *after* mapping
Expand Down Expand Up @@ -427,10 +427,8 @@ impl PlatformWindow for GraphicsWindow {
}

fn run(self: Rc<Self>) {
let event_loop = crate::eventloop::EventLoop::new();

self.clone().map_window(&event_loop);
event_loop.run();
self.clone().map_window();
crate::eventloop::run();
self.unmap_window();
}

Expand Down
20 changes: 12 additions & 8 deletions sixtyfps_runtime/rendering_backends/gl/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -423,16 +423,16 @@ pub struct GLRenderer {
}

impl GLRenderer {
pub fn new(
event_loop: &winit::event_loop::EventLoop<eventloop::CustomEvent>,
pub(crate) fn new(
event_loop: &dyn crate::eventloop::EventLoopInterface,
window_builder: winit::window::WindowBuilder,
#[cfg(target_arch = "wasm32")] canvas_id: &str,
) -> GLRenderer {
#[cfg(not(target_arch = "wasm32"))]
let (windowed_context, renderer) = {
let windowed_context = glutin::ContextBuilder::new()
.with_vsync(true)
.build_windowed(window_builder, &event_loop)
.build_windowed(window_builder, event_loop.event_loop_target())
.unwrap();
let windowed_context = unsafe { windowed_context.make_current().unwrap() };

Expand All @@ -456,7 +456,7 @@ impl GLRenderer {
};

#[cfg(target_arch = "wasm32")]
let event_loop_proxy = Rc::new(event_loop.create_proxy());
let event_loop_proxy = Rc::new(event_loop.event_loop_proxy().clone());

#[cfg(target_arch = "wasm32")]
let (window, renderer) = {
Expand All @@ -479,8 +479,12 @@ impl GLRenderer {
canvas.client_height() as u32,
);

let window =
Rc::new(window_builder.with_canvas(Some(canvas)).build(&event_loop).unwrap());
let window = Rc::new(
window_builder
.with_canvas(Some(canvas))
.build(&event_loop.event_loop_target())
.unwrap(),
);

// Try to maintain the existing size of the canvas element. A window created with winit
// on the web will always have 1024x768 as size otherwise.
Expand Down Expand Up @@ -1092,7 +1096,7 @@ impl GLFontMetrics {
#[cfg(target_arch = "wasm32")]
pub fn create_gl_window_with_canvas_id(canvas_id: String) -> ComponentWindow {
let platform_window = GraphicsWindow::new(move |event_loop, window_builder| {
GLRenderer::new(&event_loop.get_winit_event_loop(), window_builder, &canvas_id)
GLRenderer::new(event_loop, window_builder, &canvas_id)
});
let window = Rc::new(sixtyfps_corelib::window::Window::new(platform_window.clone()));
platform_window.self_weak.set(Rc::downgrade(&window)).ok().unwrap();
Expand All @@ -1116,7 +1120,7 @@ impl sixtyfps_corelib::backend::Backend for Backend {
fn create_window(&'static self) -> ComponentWindow {
let platform_window = GraphicsWindow::new(|event_loop, window_builder| {
GLRenderer::new(
&event_loop.get_winit_event_loop(),
event_loop,
window_builder,
#[cfg(target_arch = "wasm32")]
"canvas",
Expand Down

0 comments on commit 2b7a1ee

Please sign in to comment.