1 个不稳定版本
0.1.0 | 2020年6月9日 |
---|
#20 in #event-loop
用于 input_helper
24KB
208 行
event_feed
使用事件流实现的事件系统。
问题
Rust 程序中实现事件的常见模式是通过使用某种回调注册接口将boxed closure(Box<dyn FnMut(EventData)>
)传递给事件源。这通常被称为观察者模式。这种方法的优点是,通过附加这种类型的回调,库的依赖者可以扩展该库并将其集成到更大的系统中,同时保留零成本抽象,因为库本身会调用回调而不是在忙循环中轮询状态,从而消除了抽象引入的额外开销,这是 Rust 语言的主要目标之一。
然而,这种方法引入了几个新的问题
- 回调不能被重新调度或推迟,因为调用该回调的库无法了解外部代码,因此只能将管理接下来发生的事情的责任移交给回调本身,这使回调变得复杂,并引入了新的同步开销,如果程序选择使用线程(如执行复杂和/或时间关键任务时应该这样做),那么这会带来下一个影响
- 回调需要是
Send
和Sync
,如果调用回调的库对象也需要保持Send
和Sync
。这两个都是对RwLock
的要求,而Mutex
只需要Send
。然而,如果两者都不满足——这在操作系统限制锁定到主线程的对象(是的,我指的是 GDI/WGL)中很常见——库对象甚至不能放入Mutex
中。 - 还有动态调度的开销,它随着回调数量的增加而增长。由于闭包通常较短且特定,因此可以预期在长期运行中会有很多闭包,即回调抽象并不完全像定义和目标所承诺的那样零成本。
- 关于开销,回调也不会立即执行。调用包含回调的方法的不幸线程必须承担执行每个回调的负担,这会阻塞线程,阻止它执行在不幸调用之后想要执行的操作。如果工作需要在调用后立即继续,则应将其卸载,这会带来更多的复杂性和开销,因为修改代码以收集足够的回调之前,这是必要的。
这个crate的目标是使用所谓的事件流来解决这个问题。
事件流
事件流是事件的来源,可以将它们发送到事件读取器——结构体已订阅特定流,这意味着每当流有新事件时,它会将事件发送给读取器,以便它们稍后处理该事件。注意结构体是如何转向懒惰架构的。这里确实有点像迭代器的味道,这正是该方法所依赖的。事件读取器迭代地执行事件循环处理流发送给它们的事件。这里的迭代意味着读取器返回事件的迭代器,在运行时,它清理读取器的内部队列并处理事件。将.for_each()
适配器应用到迭代器上,设置一个闭包来match
事件(如果事件类型不是枚举,则按类型进行处理),你就有了一个完全解决了上述问题的回调架构版本。
让我们看看事件流如何解决上述问题
- 由于事件在生产和处理时是分开的,它们可以在任何时候处理,包括延迟处理。这允许使用完全不相关的库进行复杂的调度,这一切都是完全无痛的,因为没有任何事情是立即发生的。
- 发送部分(产生事件的库对象,即在问题案例中存储回调的部分)除了存储读取器的引用外,不存储任何内容,这意味着它始终是
Send
和Sync
。在这种情况下,唯一的特质量约是事件类型的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