#events #thread #winit #run-time #windowing #proxy #separate

nightly winit-modular

代理 winit 事件循环,可以在单独的线程中同时运行

2 个版本

0.1.1 2022 年 8 月 10 日
0.1.0 2022 年 8 月 10 日

#838 in GUI

Apache-2.0

56KB
724

winit-modular: 代理 winit 事件循环,可以在单独的线程中同时运行

提供了一个与 winit 非常相似的 API,除了 EventLoop 类型可以在多个线程上创建,可以同时存在多个事件循环,你可以轮询事件或异步接收事件,等等。

注意: 此库仍在早期开发阶段,某些功能尚未完全测试。如果您遇到任何问题,请提交 issue 或 PR 到 github

问题

winit 是在 Rust 中创建窗口和监听系统事件的事实上的方式。然而,允许您创建窗口和监听事件的核结构体 EventLoop 有一些令人烦恼的限制

  • 它必须在主线程上创建
  • 它只能创建一次
  • EventLoop::run 结束时,您的应用程序退出。

这导致更多的限制

  • 您不能同时运行多个事件循环
  • 您不能停止一个事件循环,然后运行另一个事件循环
  • 您不能有多个创建事件循环的依赖项。

还有 控制反转 的问题,winit-main 对此进行了说明并尝试解决。

解决方案

此 crate 解决了 winit 的模块化不足问题,某种程度上。它提供了与 winit::event_loop::EventLoop 相似 API 的 EventLoop,但

  • 可以在不同的线程或甚至在同一线程上存在并运行(参见 run
  • 可以异步运行(见 run_async
  • 可以被轮询(见 run_immediate),解决“控制反转”问题
  • 您可以停止对这些的调用,并丢弃事件循环而无需退出整个应用程序。

这是因为这些 EventLoop 实际上是代理,它们使用异步通道和原子操作将调用和事件从主事件循环中转发。

缺点

这并不能完全解决 winit 的问题,并不总是可以直接替换。

最重要的是,您必须在您的应用程序中,在主线程上,使用此crate中的事件循环之前,恰好调用一次 winit_modular::run。如果您不这样做,将会收到一条解释这一点的panic消息。因为 winit_modular::run 占用了主线程,它提供了一个回调来在后台线程上运行您的其余代码。

另外,使用多个线程和通过通道发送消息的性能损失。代理事件循环必须在线程边界内与实际 winit 事件循环通信,对于每个操作或拦截的事件。这意味着,通常在每一帧刷新时就会进行一次。幸运的是,现代硬件通常足够快,并且线程足够好,即使在这种情况下,它也只是一种轻微的性能损失。但在嵌入式系统中,或者如果您同时生成很多代理事件循环,可能会成为问题。

示例(原始来自 winit-main

没有 winit_modular

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

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

    event_loop.run(move |event, _, control_flow| {
        *control_flow = ControlFlow::Wait;

        if matches!(
            event,
            Event::WindowEvent {
                event: WindowEvent::CloseRequested,
                window_id,
            } if window_id == window.id()
        ) {
            *control_flow = ControlFlow::Exit;
        }
    });
}

winit-modular

use winit_modular::{
    event::{Event, WindowEvent},
    event_loop::{ControlFlow, EventLoop}
};
use pollster::block_on;

fn main() {
    winit_modular::run(|| block_on(async {
        let event_loop = EventLoop::new().await;
        
        let window = event_loop
            .create_window(|builder| builder)
            .await
            .unwrap();

        event_loop.run_async(|event, control_flow| {
            if matches!(
                event,
                Event::WindowEvent {
                    event: WindowEvent::CloseRequested,
                    window_id,
                } if window_id == window.id()
            ) {
                *control_flow = ControlFlow::ExitApp;
            }
        }).await;
    }));
}

依赖项

~3–6MB
~121K SLoC