Crates.io | event_feed |
lib.rs | event_feed |
version | 0.1.0 |
source | src |
created_at | 2020-06-09 17:37:10.19771 |
updated_at | 2020-06-09 17:37:10.19771 |
description | Event systems implemented using event feeds. |
homepage | |
repository | |
max_upload_size | |
id | 251990 |
size | 25,269 |
event_feed
Event systems implemented using event feeds.
The common pattern for implementing events in Rust prorgams is by passing a boxed closure (Box<dyn FnMut(EventData)>
) to the source of the event using some sort of callback registration interface. This is commonly referred to as the observer pattern. The main advantage of this approach is that by attaching this kind of callback, the dependents of a library can extend that library and intergrate into something bigger while retaining the zero cost abstraction, since instead of polling the state in a busy loop, the library calls the callbacks itself, removing the overhead introduced by abstraction, which is one of the main goals of Rust as a language.
This approach, however, introduces several new problems:
Send
and Sync
if the library object which calls the callback also needs to stay Send
and Sync
. Both of these are a requirement for RwLock
, while Mutex
requires only Send
. Still, if neither are upheld — which is often the case with objects locked to the main thread by operating system limitations (yes, I do mean GDI/WGL here) — the library object cannot even be put inside a Mutex
.The goal of this crate is to provide a solution to all of these problems using something called event feeds.
An event feed is the source of events which can send them to event readers — structures which are subscribed to a specific feed, which means that whenever the feed has a new event, it sends the event to the readers for them to process that event later. Notice how the structure has taken a turn towards lazy architecture. It definitely smells like iterators here, and that's exactly what the approach relies upon. The event readers iteratively perform the job of event loops processing the events which the feed sent them. Iteratively here means that the reader returns an iterator of the events, which, as it runs, cleans the inner queue of the reader and processes the events. Apply the .for_each()
adapter to the iterator, set up a closure which match
es on the events (or, if the event type is not an enumeration, processes it as applicable to the type), and you got yourself a version of the callback architecture with the aforementioned problems completely gone.
Let's see how the event feed solves the aforementioned problems:
Send
and Sync
. The only trait bound in this case is Send
for the event type — the implementation takes care of the rest.O(n)
complexity, but in this case n
is the number of readers rather than the number of callbacks, and one reader does the job of multiple or all callbacks.Basic usage:
// 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;
});
}
This crate is distributed under the Unlicense, including contributions not made by the original author.