#events #feed #event-system #event-loop

event_feed

使用事件流实现的事件系统

1 个不稳定版本

0.1.0 2020年6月9日

#20 in #event-loop


用于 input_helper

Unlicense

24KB
208

event_feed

Crates.io Docs.rs Build status

使用事件流实现的事件系统。

问题

Rust 程序中实现事件的常见模式是通过使用某种回调注册接口将boxed closure(Box<dyn FnMut(EventData)>)传递给事件源。这通常被称为观察者模式。这种方法的优点是,通过附加这种类型的回调,库的依赖者可以扩展该库并将其集成到更大的系统中,同时保留零成本抽象,因为库本身会调用回调而不是在忙循环中轮询状态,从而消除了抽象引入的额外开销,这是 Rust 语言的主要目标之一。

然而,这种方法引入了几个新的问题

  • 回调不能被重新调度或推迟,因为调用该回调的库无法了解外部代码,因此只能将管理接下来发生的事情的责任移交给回调本身,这使回调变得复杂,并引入了新的同步开销,如果程序选择使用线程(如执行复杂和/或时间关键任务时应该这样做),那么这会带来下一个影响
  • 回调需要是 SendSync,如果调用回调的库对象也需要保持 SendSync。这两个都是对 RwLock 的要求,而 Mutex 只需要 Send。然而,如果两者都不满足——这在操作系统限制锁定到主线程的对象(是的,我指的是 GDI/WGL)中很常见——库对象甚至不能放入 Mutex 中。
  • 还有动态调度的开销,它随着回调数量的增加而增长。由于闭包通常较短且特定,因此可以预期在长期运行中会有很多闭包,即回调抽象并不完全像定义和目标所承诺的那样零成本。
  • 关于开销,回调也不会立即执行。调用包含回调的方法的不幸线程必须承担执行每个回调的负担,这会阻塞线程,阻止它执行在不幸调用之后想要执行的操作。如果工作需要在调用后立即继续,则应将其卸载,这会带来更多的复杂性和开销,因为修改代码以收集足够的回调之前,这是必要的。

这个crate的目标是使用所谓的事件流来解决这个问题。

事件流

事件流是事件的来源,可以将它们发送到事件读取器——结构体已订阅特定流,这意味着每当流有新事件时,它会将事件发送给读取器,以便它们稍后处理该事件。注意结构体是如何转向懒惰架构的。这里确实有点像迭代器的味道,这正是该方法所依赖的。事件读取器迭代地执行事件循环处理流发送给它们的事件。这里的迭代意味着读取器返回事件的迭代器,在运行时,它清理读取器的内部队列并处理事件。将.for_each()适配器应用到迭代器上,设置一个闭包来match事件(如果事件类型不是枚举,则按类型进行处理),你就有了一个完全解决了上述问题的回调架构版本。

让我们看看事件流如何解决上述问题

  • 由于事件在生产和处理时是分开的,它们可以在任何时候处理,包括延迟处理。这允许使用完全不相关的库进行复杂的调度,这一切都是完全无痛的,因为没有任何事情是立即发生的。
  • 发送部分(产生事件的库对象,即在问题案例中存储回调的部分)除了存储读取器的引用外,不存储任何内容,这意味着它始终是SendSync。在这种情况下,唯一的特质量约是事件类型的Send——实现负责其余部分。
  • 不需要动态分派——读取器仅使用一个闭包来处理事件,该闭包可以组合多种不同类型的事件的处理程序,或者为一种类型的事件执行多个操作。
  • 将事件发布到流中的复杂度仍然是O(n),但在这种情况下,n是读取器的数量而不是回调的数量,一个读取器做多个或所有回调的工作。

使用

基本使用

// Use the prelude module to quickly access the types in a renamed version which avoids name collisions.
use event_feed::prelude::*;

// Create the feed. Initially it has no readers.
let mut feed = EventFeed::new();
// Create the reader to read events from the feed.
let reader = feed.add_reader();
// Send an event through the feed.
feed.send("Hello event feed!");

// We can now read the event we sent.
assert_eq!(
    reader.read().next(),
    Some("Hello event feed!"),
);
// There are no more events in the feed.
assert_eq!(
    reader.read().next(),
    None,
);
// Send another event.
feed.send("This event will be displayed through a handler closure!");
feed.send("There are multiple events to display!");

// Read multiple events using a closure.
{
    let mut event_number = 0;
    reader.read_with(|event| {
        println!(
            "Event number {num} says: {evt}",
            num = event_number,
            evt = event,
        );
        event_number += 1;
    });
}

许可证

此crate在Unlicense下分发,包括原作者未做的贡献。

依赖项

~0.5–0.8MB
~13K SLoC