Use winit like the async runtime you've always wanted

Overview

async-winit

Use winit like the async runtime you've always wanted.

winit is actually asynchronous, contrary to popular belief; it's just not async. It uses an event loop to handle events, which is an good fit for some cases but not others. The maintainers of winit have referred to this type of event loop as "poor man's async"; a system that is not async but is still asynchronous.

This crate builds an async interface on top of this event loop.

Example

Consider the following winit program, which creates a window and prints the size of the window when it is resized:

use winit::event::{Event, WindowEvent};
use winit::event_loop::EventLoop;
use winit::window::Window;

fn main2(evl: EventLoop<()>) {
    let mut window = None;

    evl.run(move |event, elwt, flow| {
        match event {
            Event::Resumed => {
                // Application is active; create a window.
                window = Some(Window::new(elwt).unwrap());
            },

            Event::Suspended => {
                // Application is inactive; destroy the window.
                window = None;
            },

            Event::WindowEvent { event, .. } => match event {
                WindowEvent::CloseRequested => {
                    // Window is closed; exit the application.
                    flow.set_exit();
                },

                WindowEvent::Resized(size) => {
                    println!("{:?}", size);
                }

                _ => {},
            },

            _ => {},
        }
    });
}

fn main() {
#   return;
    let evl = EventLoop::new();
    main2(evl);
}

This strategy is a bit long winded. Now, compare against the equivalent async-winit program:

use async_winit::event_loop::EventLoop;
use async_winit::window::Window;
use futures_lite::prelude::*;

fn main2(evl: EventLoop) {
    let window_target = evl.window_target().clone();

    evl.block_on(async move {
        loop {
            // Wait for the application to be active.
            window_target.resumed().await;

            // Create a window.
            let window = Window::new().await.unwrap();

            // Print the size of the window when it is resized.
            let print_size = async {
                window
                    .resized()
                    .wait_many()
                    .for_each(|size| {
                        println!("{:?}", size);
                    })
                    .await;

                true
            };

            // Wait until the window is closed.
            let close = async {
                window.close_requested().wait_once().await;
                println!("Close");
                true
            };

            // Wait until the application is suspended.
            let suspend = async {
                window_target.suspended().wait_once().await;
                false
            };

            // Run all of these at once.
            let needs_exit = print_size.or(close).or(suspend).await;

            // If we need to exit, exit. Otherwise, loop again, destroying the window.
            if needs_exit {
                window_target.exit().await;
            } else {
                drop(window);
            }
        }
    });
}

fn main() {
#   return;
    let evl = EventLoop::new();
    main2(evl);
}

In my opinion, the flatter async style is much easier to read and understand. Your mileage may vary.

Pros

  • In many cases it may make more sense to think of a program as an async task, rather than an event loop.
  • You don't need to tie everything to the EventLoopWindowTarget; Window::new() and other functions take no parameters and can be called from anywhere as long as an EventLoop is running somewhere.
  • You can use the async ecosystem to its full potential here.

Cons

  • There is a not insignificant amount of overhead involved in using async-winit. This is because async-winit is built on top of winit, which is built on top of winit's event loop. This means that async-winit has to convert between async and winit's event loop, which is not free.
  • async-winit is not as low level as winit. This means that you can't do everything that you can do with winit.

Credits

async-winit was created by John Nunley (@notgull).

This project is heavily based on async-io by Stjepan Glavina et al, as well as winit by Pierre Kreiger et al.

License

async-winit is free software: you can redistribute it and/or modify it under the terms of one of the following licenses:

async-winit is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License and the Patron License for more details.

You should have received a copy of the GNU Affero General Public License and the corresponding Patron License along with async-winit. If not, see https://www.gnu.org/licenses/.

You might also like...
BPF library for Async Rust, complementary for libbpf-rs.

libbpf-async A library for writing BPF programs in Async Rust, complementary for libbpf-rs, Rust wrapper for libbpf. Currently, this provides Async-fr

Async `TryFrom/TryInto` traits

async-convert Async TryFrom/TryInto traits API Docs | Releases | Contributing Installation $ cargo add async-convert Safety This crate uses #![deny(un

Simple async codec for rkyv. Reuses streaming buffer for maximum speed

rkyv_codec Simple async codec for rkyv. Reuses streaming buffer for maximum speed! This crate provides a makeshift adaptor for streaming &ArchivedObj

You can name anonymous Future from async fn without dyn or Box!

rename-future You can name anonymous Future from async fn without dyn or Box! PLEASE READ THIS THIS PROJECT NOT YET WELL TESTED! DON'T USE THIS IN PRO

The most fundamental type for async synchronization: an intrusive linked list of futures

wait-list This crate provides WaitList, the most fundamental type for async synchronization. WaitList is implemented as an intrusive linked list of fu

Ector is an open source async, no-alloc actor framework for embedded devices

Ector is an open source async, no-alloc actor framework for embedded devices. Ector is an open source async, no-alloc actor framework for embedded dev

Async variant of Tonic's `interceptor` function

Tonic Async Interceptor This crate contains AsyncInterceptor, an async variant of Tonic's Interceptor. Other than accepting an async interceptor funct

Rust library for one-time async initialization

async-lazy An async version of once_cell::sync::Lazy, std::sync::OnceLock or lazy_static. Uses tokio's sychronization primitives. This crate offers an

An efficient async condition variable for lock-free algorithms

async-event An efficient async condition variable for lock-free algorithms, a.k.a. "eventcount". Overview Eventcount-like primitives are useful to mak

Comments
  • Fix README example

    Fix README example

    Hey cool crate! Just found out about it the other day on mastodon.

    I'll try to use it for some things I want to learn that use winit in the examples. So far it seems really cool and promising!

    It took me a little while to figure out how to use the crate though, since the README example has some typos and that's where I generally look before looking into the example directory.

    In this PR I fixed the second README example and made sure it compiles.

    Thanks for the crate again!

    opened by RobWalt 0
  • Further Limitations with mutable access

    Further Limitations with mutable access

    Playing around with the lib, I unfortunately found the following limitation 😢

    
     // create some wgpu_context bundling stuff for the gfx pipeline
    // ...
    
                let resized = async {
                    window
                        .resized()
                        .wait_many()
                        .for_each(|size| {
                            wgpu_context.resize(size);
                        })
                        .await;
                    true
                };
    
                let redraw = async {
                    window
                        .redraw_requested()
                        .wait_many()
                        .for_each(|_| {
                            wgpu_context.render().expect("Everything is fine .. 🔥");
                        })
                        .await;
                    true
                };
    // ...
    

    This example code doesn't compile since both the resize and render actions need mutable access to the wgp_contexts fields. The code is running without bundling everything into the struct but it limirs the ability to abstract all the low level details away. If there is a way to get the code above running I would be very happy but I see no easy way of doing that. If you also don't have an idea how to run that code, we could maybe mention that in the "Cons" part of the README. There is no free lunch after all 😢 🥖 ❌

    opened by RobWalt 3
Owner
John Nunley
Blinded by the light
John Nunley
Spot coupling by finding out which files are always in the same commit

git moves-together This tells you when files in the repository frequently move together. This lets you identify where the coupling is in the system. C

Billie Thompson 14 Oct 31, 2022
Ever wanted to torture your CPU by generating every image possible?

dumpsterfire Ever wanted to torture your CPU by generating every image possible? Well, now you can! This thing is worse than mining Bitcoin, since the

null 2 Jun 14, 2022
Graceful shutdown util for Rust projects using the Tokio Async runtime.

Shutdown management for graceful shutdown of tokio applications. Guard creating and usage is lock-free and the crate only locks when: the shutdown sig

Plabayo 54 Sep 29, 2023
Golang like WaitGroup implementation for sync/async Rust.

wg Golang like WaitGroup implementation for sync/async Rust.

Al Liu 8 Dec 6, 2022
Async executor for WebAssembly

There are a number of async task executors available in Rust's ecosystem. However, most (if not all?) of them rely on primitives that might not be available or optimal for WebAssembly deployment at the time.

wasm.rs 65 Dec 31, 2022
The feature-rich, portable async channel library

The feature-rich, portable async channel library > crates.io > docs.rs Why use Postage? Includes a rich set of channels. | barrier | broadcast | dispa

Austin Jones 221 Dec 26, 2022
Mix async code with CPU-heavy thread pools using Tokio + Rayon

tokio-rayon Mix async code with CPU-heavy thread pools using Tokio + Rayon Resources Documentation crates.io TL;DR Sometimes, you're doing async stuff

Andy Barron 74 Jan 2, 2023
Another Async IO Framework based on io_uring

kbio, the Async IO Framework based on io_uring, is used in KuiBaDB to implement async io. Features Support multi-threading concurrent task submission.

KuiBaDB 59 Oct 31, 2022
async-alloc-counter measures max allocations in a future invocation

async-alloc-counter measures max allocations in a future invocation see examples/ for usage This allocator can be used as follows: use async_alloc_cou

Geoffroy Couprie 2 Dec 3, 2021
single file, std only, async Rust executor

whorl - A single file, std only, async Rust executor whorl was created to teach you how async executors work in Rust. It is not the fastest executor n

Michael Gattozzi 459 Dec 29, 2022