#gui-applications #events #loops #cross-platform-gui #dispatcher #message

thin_main_loop

轻量级、跨平台的主事件循环。它是原生GUI应用以及其他用例的构建块。

8 个版本

0.2.0 2019年11月7日
0.1.7 2019年5月15日
0.1.6 2019年4月21日
0.1.5 2019年3月2日
0.1.2 2019年1月27日

#514 in 异步

Apache-2.0/MIT

55KB
1K SLoC

Rust的轻量级主循环

因为Rust的本地GUI故事从主循环开始。

(尽管这个库也可能适用于许多其他用例。)

目标

  • 支持基于回调和async/await的API
  • 跨平台
  • 桌面应用程序的性能开销可忽略不计
  • 绑定到每个平台上的最佳后端
  • 没有额外的后台线程
  • 提供对原始句柄的访问以允许平台特定的扩展

非目标

  • 不惜一切代价避免分配和虚拟调度
  • I/O可伸缩性
  • no_std功能

状态

该库具有运行代码的功能

  • ASAP(主循环有机会运行时),
  • 超时后,
  • 定期间隔,
  • ASAP,但在另一个线程中,
  • 当I/O对象准备好读取或写入时。

它可以通过以下方式实现:

  • 安排回调
  • 安排0.3 future
  • 安排async fn

成熟度:刚刚运行起来,未经战斗测试。它也是一个概念验证,以引发讨论和兴趣。也就是说,它正在等待您给它一个机会,尝试它,看看您喜欢什么,不喜欢什么,缺少哪些功能等!

不安全块:仅在后端/FFI级别。在引用(Rust std)后端中,根本不存在不安全代码。

Rust版本:最新稳定版应该没问题。

支持的平台

目前

  • Win32 API - 使用--features "win32"编译
  • Glib - 使用--features "glib"编译
  • Rust std - 参考实现,不支持I/O。

愿望清单

  • OS X / Cocoa
  • Wasm / web(由于我们无法控制主循环,因此有限)
  • QT
  • iOS
  • Android

示例

借用

如果您有权访问主循环,它支持借用闭包,因此您不需要克隆/refcell您的数据

// extern crate thin_main_loop as tml;

let mut x = false;
{
    let mut ml = tml::MainLoop::new();
    ml.call_asap(|| {
        x = true; // x is mutably borrowed by the closure
        tml::terminate();
    });
    ml.run();
}
assert_eq!(x, true);

非借用,以及一个定时器

如果您没有访问主循环的权限,您仍然可以安排 'static 回调

// extern crate thin_main_loop as tml;

let mut ml = tml::MainLoop::new();
ml.call_asap(|| {
    // Inside a callback we can schedule another callback.
    tml::call_after(Duration::new(1, 0), || {
        tml::terminate();
    });
})
ml.run();
// After one second, the main loop is terminated.

I/O

需要“glib”或“win32”功能。

以下示例连接到TCP服务器并打印所有传入的数据。

// extern crate thin_main_loop as tml;

let mut io = TcpStream::connect(/* ..select server here.. */)?;
io.set_nonblocking(true)?;
let wr = tml::IOReader { io: io, f: move |io: &mut TcpStream, x| {
    // On incoming data, read it all
    let mut s = String::new();
    let r = io.read_to_string(&mut s);

    // If we got something, print it
    if s != "" { println!(s); }

    // This is TcpStream's way of saying "connection closed"
    if let Ok(0) = r { tml::terminate(); }
}

let mut ml = MainLoop::new()?;
ml.call_io(wr)?;
ml.run();

异步函数

以下代码等待一秒后终止程序。

// extern crate thin_main_loop as tml;

use std::time::{Instant, Duration};

async fn wait_until(n: Instant) {
    delay(n).await.unwrap();
}

let mut x = tml::futures::Executor::new().unwrap();
let n = Instant::now() + Duration::from_millis(1000);
x.block_on(wait_until(n));

后台

回调

大多数本地GUI的API都是基于回调构建的。当按钮被点击时,你会得到一个回调。但问题是,回调实际上并不 rustic:如果你想在两个不同的回调中访问你的按钮对象,你最终不得不使用 Rc/RefCell。虽然这并不是世界末日,但人们一直在尝试其他更 rustic 的 API 设计。

但这将是另一个库的任务。如果我们首先创建一个薄薄的跨平台库,该库绑定到本地GUI API,我们就可以在此基础上尝试创建一个更 rustic 的 API。在我们能够创建窗口和按钮之前,我们需要创建一个主循环,它可以处理这些对象的事件。因此,这个库。

其他Rust主循环

Mio

Mio 也是一个跨平台的主循环,但Mio的设计目标与最终使其不适用于本地GUI应用程序的目标大不相同。Mio的主要用例是高度可扩展的服务器,因此它绑定到 IOCP/epoll 等,可以无问题处理数千个TCP套接字,但它与GUI库的本地API集成不佳:IOCP 线程无法处理 Windows 消息等。这个库绑定到 PeekMessage/GMainLoop 等,这使得它适用于具有本地外观和感觉的GUI应用程序。

此外,Mio在避免分配方面做得更好,但代价是牺牲了易用性。

Calloop

Calloop 是一个具有与这个crate非常相似的回调式API的事件循环。然而,它是建立在Mio之上的,因此它绑定到了不适用于本地GUI应用程序的本地API。

Winit

Winit 包含一个事件循环,该crate的目的是创建窗口。事件循环不是基于回调的,而是基于枚举(每个事件都是一个枚举,你需要自己进行分发)。Winit的重点更在于获取窗口和自定义绘图(通过OpenGL,Vulkan等),而不是绘制本地GUI小部件,但尽管如此,它仍与这个crate有一些共同点。

IUI / libui

IUI 是 Rust 对 libui 的绑定,libui 是用 C 编写的跨平台 GUI 库。其事件循环提供回调,就像这个库一样。相比之下,这个库是纯Rust,并直接绑定到本地库,跳过了一个抽象层,因此更容易构建。我也希望随着时间的推移,这个库能够提供更好的 Rust 集成以及更多的灵活性,使其不仅适用于纯GUI应用程序,即使当前的主要用例是如此。

依赖项

~0.6–3MB
~58K SLoC