Eduardo Porto
Eduardo Porto

Reputation: 77

Single threaded asynchronous event loop with `winit`

I'm trying to build an NES emulator using winit, which entails building a game loop which should run exactly 60 times per second.

At first, I used std::thread to create a separate thread where the game loop would run and wait 16 milliseconds before running again. This worked quite well, until I tried to compile the program again targeting WebAssembly. I then found out that both winit::window::Window and winit::event_loop::EventLoopProxy are not Send when targeting Wasm, and that std::thread::spawn panics in Wasm.

After some struggle, I decided to try to do the same thing using task::spawn_local from one of the main asynchronous runtimes. Ultimately, I went with async_std.

I'm not used to asynchronous programming, so I'm not even sure if what I'm trying to do could work.

My idea is to do something like this:

use winit::{window::WindowBuilder, event_loop::EventLoop};
use std::time::Duration;

fn main() {
    let event_loop = EventLoop::new();
    let _window = WindowBuilder::new()
        .build(&event_loop);

    async_std::task::spawn_local(async {
        // game loop goes here
        loop {
            // [update game state]
            // [update frame buffer]
            // [send render event with EventLoopProxy]
            async_std::task::sleep(Duration::from_millis(16)).await;
            // ^ note: I'll be using a different sleep function with Wasm
        }
    });

    event_loop.run(move |event, _, control_flow| {
        control_flow.set_wait();
        match event {
            // ...
            _ => ()
        }
    });
}

The problem with this approach is that the game loop will never run. If I'm not mistaken, some asynchronous code in the main thread would need to be blocked (by calling .await) for the runtime to poll other Futures, such as the one spawned by the spawn_local function. I can't do this easily, since event_loop.run is not asynchronous.

Having time to await other events shouldn't be a problem, since the control flow is set to wait.

Testing this on native code, nothing inside the game loop ever runs. Testing this on Wasm code (with wasm_timer::Delay as the sleep function), the game loop does run, but at a very low framerate and with long intervals of halting.

Having explained my situation, I would like to ask: is it possible to do what I'm trying to do, and if it is, how would I approach it? I will also accept answers telling me how I could try to do this differently, such as by using web workers.

Thanks in advance!

Upvotes: 3

Views: 999

Answers (0)

Related Questions