Skip to content

Instantly share code, notes, and snippets.

@felixrabe
Forked from Osspial/020.md
Last active June 21, 2019 20:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save felixrabe/13040ca9a9affd5864bedd2995ddcc83 to your computer and use it in GitHub Desktop.
Save felixrabe/13040ca9a9affd5864bedd2995ddcc83 to your computer and use it in GitHub Desktop.

Winit 0.20 Alpha 1

Hello, all!

I'm one of the maintainers of Winit, the main pure-Rust window creation library. Even if you haven't used it directly, you've probably heard of projects that depend on it - Servo and Alacritty being the best-known applications that depend on our codebase. If you've done any graphics programming in Rust using Glutin (or dependent projects including gfx-rs, Glium, and Amethyst) we've been the ones making the windows actually show up on your desktop.

This announcement details the major changes since Winit 0.19. Also, we are looking for new contributors! If you are interested in working on the foundations of Rust's GUI story, now is a great time to join the project.

The new event loop

The highlight of this release is a major overhaul of Winit's Event Loop API. This change vastly improves Winit's stability and introduces quality-of-life upgrades, such as native timer support and an API for injecting custom user events into the Winit event loop. For example: (TODO: Link to full example on GitHub.)

#[derive(Debug, Clone, Copy)]
enum CustomEvent {
    Timer,
}

let event_loop = EventLoop::<CustomEvent>::new_user_event();
// `EventLoopProxy` allows dispatching custom events
// to the Winit event loop from any thread.
let event_loop_proxy = event_loop.create_proxy();

let timer_length = Duration::from_secs(1);

event_loop.run(move |event, _, control_flow| {
    match event {
        // When the event loop starts running, queue the timer.
        Event::NewEvents(StartCause::Init) => {
            *control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length);
        },

        // When the timer expires, dispatch a timer event and queue a new timer.
        Event::NewEvents(StartCause::ResumeTimeReached{..}) => {
            event_loop_proxy.send_event(CustomEvent::Timer).ok();
            *control_flow = ControlFlow::WaitUntil(Instant::now() + timer_length);
        },

        Event::UserEvent(event) => println!("user event: {:?}", event),

        Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => {
            *control_flow = ControlFlow::Exit;
        },

        _ => ()
    }
});

This release also cleans up Winit's API, improving both its internal consistency and its consistency with the rest of the Rust ecosystem.

Why an Alpha release?

There are a few reasons we're introducing this as an alpha:

  1. All implementations still need thorough testing, and some platforms still have major, application-breaking bugs.
  2. We'd like to merge an overhaul of Winit's HiDPI API, but the overhaul is only implemented on Windows.

However, we lack the personnel necessary to implement those changes!

A call for help

That segues nicely into the second point I'd like to bring up: for such a core piece of infrastructure in the Rust ecosystem, we have astonishingly little manpower. Four (TODO: five?) of the seven platforms we support lack active maintainers: X11, macOS, Android, iOS, and WASM are all largely unmaintained. Additionally, Glutin is currently maintained by just a single person, despite being a project no lone individual has the time to handle. This had led to a great deal of issue buildup on those platforms, since we lack contributors with enough time to debug and resolve issues on those platforms. Major new features and API improvements can't land on crates.io since we currently can't implement them across all platforms - gamepad support being the most significant (although certainly not only) example.

If you are interested in Rust becoming a larger force in graphics ecosystem, please take a look at the issues below and find one you can contribute to! We encourage all types of contributions, so go out and write, test, review, and submit PRs, add and review documentation, and whatever else you would like to do. No matter what you do, your time would be very much appreciated, and would result in widespread improvements across the Rust GUI community.

High-level issues:

FAQ

Why remove poll_events completely?

poll_events only functions as expected on Linux.

All other platforms break in some way with applications built around poll_events - some breaking in subtle ways, others breaking completely. For example, when a user resizes the application's window, Windows* and macOS freeze the main event loop completely if the event loop uses poll_events. On the more extreme end, Mobile and Web platforms don't ever return from poll_events, completely breaking any application that relies on poll_events functioning.

As much as we'd like to expose poll_events as an API, the reality is that we cannot expose it without fundamentally lying about its cross-platform functionality.

* You might notice that calling poll_events on Windows in legacy Winit doesn't freeze the event loop. This is because legacy Winit spawns an entire background thread to run the Windows event loop in order to hide the freezing behavior. This has caused innumerable amounts of stability and UX problems, and moving to the new event loop model is the only way to fix those issues without creating an unstable tower of hacks.

Since poll_events is dead, is there any sort of replacement?

Yep! The run_forever API has been renamed to run, and received a few major usability upgrades:

  1. You now receive an event when the platform event queue has been drained, allowing you to bundle your application logic into a single event handler.
  2. New windows can be created within the event handler closure, using the EventLoopWindowTarget field.
  3. You can now set timers in the event loop, adding proper support for framerate limiters and other timer-based functionality.

These new features make run actually usable in a real application, and should more than compensate for poll_events' removal.

Why does the new run function not return?

It was the only way for us to expose a single API that more or less behaves the same way on all platforms.

Admittedly, it isn't necessary on desktop platforms (Windows, macOS, and Linux). However, Android, iOS, and WASM all require the host platform take exclusive control over the application's event loop, and this API allows us to expose that behavior under the same API as our desktop backends.

If, for whatever reason, you absolutely must be able to return from the run function, we expose [run_return](LINK TO DOCS) on our desktop platforms. We'd discourage using that unless absolutely necessary, since it breaks cross-platform compatibility and can lead to subtle bugs on desktop platforms if used improperly.

Why break the API's backwards compatibility now?

There were several small details in Winit's public API that we weren't happy with, but required breaking changes to fix. Since we were already breaking downstream applications with the new event loop API, we decided to try bundling all our desired breaking changes into a single release instead of painfully staggering them out over several point releases. This will make upgrading to 0.20 a bit more of a hassle, but should make the API significantly easier to use going forward.

Unfortunately we didn't quite manage to include all the breaking changes we wanted this release, but breaking changes in the future should be significantly less disruptive and will be bundled with major feature releases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment