├── .gitignore
├── .media
└── showcase.png
├── .scripts
└── build.sh
├── .travis.yml
├── Cargo.toml
├── LICENSE
├── README.md
├── core
├── Cargo.toml
└── src
│ ├── display
│ ├── mod.rs
│ └── skia.rs
│ ├── error.rs
│ └── lib.rs
├── derive
├── Cargo.toml
└── src
│ ├── event.rs
│ └── lib.rs
├── event
├── Cargo.toml
├── README.md
├── benches
│ └── events.rs
└── src
│ ├── bidir.rs
│ ├── bidir_single.rs
│ ├── cascade
│ ├── mod.rs
│ └── utils.rs
│ ├── chans.rs
│ ├── dchans.rs
│ ├── intern.rs
│ ├── lib.rs
│ ├── macros.rs
│ ├── merge.rs
│ ├── nonrc.rs
│ ├── nonts.rs
│ ├── streaming
│ ├── direct.rs
│ ├── mod.rs
│ └── wrapper.rs
│ ├── thirdparty.rs
│ ├── traits.rs
│ └── ts.rs
├── reclutch
├── Cargo.toml
├── examples
│ ├── counter
│ │ └── main.rs
│ ├── image_viewer
│ │ ├── ferris.png
│ │ ├── image.jpg
│ │ └── main.rs
│ ├── opengl
│ │ └── main.rs
│ └── shaping
│ │ ├── NotoSans.ttf
│ │ └── main.rs
└── src
│ └── lib.rs
├── rustfmt.toml
└── verbgraph
├── Cargo.toml
└── src
└── lib.rs
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | **/target
3 | **/*.rs.bk
4 | **/.#*
5 | /.idea
6 | Cargo.lock
7 | .vscode/
8 |
--------------------------------------------------------------------------------
/.media/showcase.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jazzfool/reclutch/2fbd297fbc007f293ea6e5f5baff4ee0c95812d3/.media/showcase.png
--------------------------------------------------------------------------------
/.scripts/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | rdx() {
4 | echo "\$" "$@"
5 | "$@"
6 | }
7 |
8 | rdx cargo build --verbose || exit 1
9 | echo
10 | rdx cd event || exit 1
11 | rdx cargo test --tests --verbose --features "crossbeam-channel"
12 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: rust
2 | cache: cargo
3 | rust:
4 | - stable
5 | - beta
6 | - nightly
7 | jobs:
8 | allow_failures:
9 | - rust: nightly
10 | fast_finish: true
11 | script:
12 | - bash .scripts/build.sh
13 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | members = [
3 | "core",
4 | "event",
5 | "derive",
6 | "verbgraph",
7 | "reclutch",
8 | ]
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Licensed under either "Apache 2.0" (http://www.apache.org/licenses/LICENSE-2.0)
2 | or "MIT" (http://opensource.org/licenses/MIT), at your option.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reclutch
2 |
3 | [](https://travis-ci.com/jazzfool/reclutch)
4 |
5 | A strong foundation for building predictable and straight-forward Rust UI toolkits. Reclutch is:
6 |
7 | - **Bare:** Very little UI code is included. In practice it's a utility library which makes very little assumptions about the toolkit or UI.
8 | - **Platform-agnostic:** Although a default display object is provided, the type of display object is generic, meaning you can build for platforms other than desktop. For example you can create web applications simply by using DOM nodes as display objects while still being efficient, given the retained-mode design.
9 | - **Reusable:** Provided structures such as unbound queue handlers allow for the reuse of common logical components across widgets.
10 |
11 | ## Overview
12 |
13 | Reclutch implements the well-known retained-mode widget ownership design within safe Rust, following along the footsteps of popular desktop frameworks. To implement this behavior, three core ideas are implemented:
14 |
15 | - A widget ownership model with no middleman, allowing widgets to mutate children at any time, but also collect children as a whole to make traversing the widget tree a trivial task.
16 | - A robust event queue system with support for `futures`, `crossbeam` and `winit` event loop integration, plus a multitude of queue utilities and queue variations for support in any environment.
17 | - An event queue abstraction to facilitate just-in-time event coordination between widgets, filling any pitfalls that may arise when using event queues. Beyond this, it also moves the code to handle queues to the constructor, presenting an opportunity to modularize and reuse logic across widgets.
18 |
19 |
20 |
21 |
22 |
23 | ### Note for MacOS
24 |
25 | There appears to be a bug with shared OpenGL textures on MacOS. As a result, the `opengl` example won't work correctly. For applications that require rendering from multiple contexts into a single texture, consider using Vulkan or similar.
26 |
27 | ### _Also see:_
28 |
29 | - [Events and event queues](event/README.md)
30 | - [Otway](https://github.com/jazzfool/otway)
31 |
32 | ## Example
33 |
34 | All rendering details have been excluded for simplicity.
35 |
36 | ```rust
37 | #[derive(WidgetChildren)]
38 | struct Button {
39 | pub button_press: RcEventQueue<()>,
40 | graph: VerbGraph,
41 | }
42 |
43 | impl Button {
44 | pub fn new(global: &mut RcEventQueue) -> Self {
45 | Button {
46 | button_press: RcEventQueue::new(),
47 | global_listener: VerbGraph::new().add(
48 | "global",
49 | QueueHandler::new(global).on("click", |button, _aux, _event: WindowEvent| {
50 | button.button_press.emit_owned(());
51 | }),
52 | ),
53 | }
54 | }
55 | }
56 |
57 | impl Widget for Button {
58 | type UpdateAux = ();
59 | type GraphicalAux = ();
60 | type DisplayObject = DisplayCommand;
61 |
62 | fn bounds(&self) -> Rect { /* --snip-- */ }
63 |
64 | fn update(&mut self, aux: &mut ()) {
65 | // Note: this helper function requires that `HasVerbGraph` be implemented on `Self`.
66 | reclutch_verbgraph::update_all(self, aux);
67 | // The equivalent version which doesn't require `HasVerbGraph` is;
68 | let mut graph = self.graph.take().unwrap();
69 | graph.update_all(self, aux);
70 | self.graph = Some(graph);
71 | }
72 |
73 | fn draw(&mut self, display: &mut dyn GraphicsDisplay, _aux: &mut ()) { /* --snip-- */ }
74 | }
75 | ```
76 |
77 | The classic counter example can be found in examples/overview.
78 |
79 | ---
80 |
81 | ## Children
82 |
83 | Children are stored manually by the implementing widget type.
84 |
85 | ```rust
86 | #[derive(WidgetChildren)]
87 | struct ExampleWidget {
88 | #[widget_child]
89 | child: AnotherWidget,
90 | #[vec_widget_child]
91 | children: Vec,
92 | }
93 | ```
94 |
95 | Which expands to exactly...
96 |
97 | ```rust
98 | impl reclutch::widget::WidgetChildren for ExampleWidget {
99 | fn children(
100 | &self,
101 | ) -> Vec<
102 | &dyn reclutch::widget::WidgetChildren<
103 | UpdateAux = Self::UpdateAux,
104 | GraphicalAux = Self::GraphicalAux,
105 | DisplayObject = Self::DisplayObject,
106 | >,
107 | > {
108 | let mut children = Vec::with_capacity(1 + self.children.len());
109 | children.push(&self.child as _);
110 | for child in &self.children {
111 | children.push(child as _);
112 | }
113 | children
114 | }
115 |
116 | fn children_mut(
117 | &mut self,
118 | ) -> Vec<
119 | &mut dyn reclutch::widget::WidgetChildren<
120 | UpdateAux = Self::UpdateAux,
121 | GraphicalAux = Self::GraphicalAux,
122 | DisplayObject = Self::DisplayObject,
123 | >,
124 | > {
125 | let mut children = Vec::with_capacity(1 + self.children.len());
126 | children.push(&mut self.child as _);
127 | for child in &mut self.children {
128 | children.push(child as _);
129 | }
130 | children
131 | }
132 | }
133 | ```
134 |
135 | (Note: you can switch out the `reclutch::widget::WidgetChildren`s above with your own trait using `#[widget_children_trait(...)]`)
136 |
137 | Then all the other functions (`draw`, `update`, maybe even `bounds` for parent clipping) are propagated manually (or your API can have a function which automatically and recursively invokes for both parent and child);
138 |
139 | ```rust
140 | fn draw(&mut self, display: &mut dyn GraphicsDisplay) {
141 | // do our own rendering here...
142 |
143 | // ...then propagate to children
144 | for child in self.children_mut() {
145 | child.draw(display);
146 | }
147 | }
148 | ```
149 |
150 | **Note:** `WidgetChildren` requires that `Widget` is implemented.
151 |
152 | The derive functionality is a feature, enabled by default.
153 |
154 | ## Rendering
155 |
156 | Rendering is done through "command groups". It's designed in a way that both a retained-mode renderer (e.g. WebRender) and an immediate-mode renderer (Direct2D, Skia, Cairo) can be implemented.
157 | The API also supports Z-Order.
158 |
159 | ```rust
160 | struct VisualWidget {
161 | command_group: CommandGroup,
162 | }
163 |
164 | impl Widget for VisualWidget {
165 | // --snip--
166 |
167 | fn update(&mut self, _aux: &mut ()) {
168 | if self.changed {
169 | // This simply sets an internal boolean to "true", so don't be afraid to call it multiple times during updating.
170 | self.command_group.repaint();
171 | }
172 | }
173 |
174 | // Draws a nice red rectangle.
175 | fn draw(&mut self, display: &mut dyn GraphicsDisplay, _aux: &mut ()) {
176 | let mut builder = DisplayListBuilder::new();
177 | builder.push_rectangle(
178 | Rect::new(Point::new(10.0, 10.0), Size::new(30.0, 50.0)),
179 | GraphicsDisplayPaint::Fill(Color::new(1.0, 0.0, 0.0, 1.0).into()),
180 | None);
181 |
182 | // Only pushes/modifies the command group if a repaint is needed.
183 | self.command_group.push(display, &builder.build(), Default::default(), None, true);
184 |
185 | draw_children();
186 | }
187 |
188 | // --snip--
189 | }
190 | ```
191 |
192 | ## Updating
193 |
194 | The `update` method on widgets is an opportunity for widgets to update layout, animations, etc. and more importantly handle events that have been emitted since the last `update`.
195 |
196 | Widgets have an associated type; `UpdateAux` which allows for a global object to be passed around during updating. This is useful for things like updating a layout.
197 |
198 | Here's a simple example;
199 |
200 | ```rust
201 | type UpdateAux = Globals;
202 |
203 | fn update(&mut self, aux: &mut Globals) {
204 | if aux.layout.node_is_dirty(self.layout_node) {
205 | self.bounds = aux.layout.get_node(self.layout_node);
206 | self.command_group.repaint();
207 | }
208 |
209 | self.update_animations(aux.delta_time());
210 |
211 | // propagation is done manually
212 | for child in self.children_mut() {
213 | child.update(aux);
214 | }
215 |
216 | // If your UI doesn't update constantly, then you must check child events *after* propagation,
217 | // but if it does update constantly, then it's more of a micro-optimization, since any missed events
218 | // will come back around next update.
219 | //
220 | // This kind of consideration can be avoided by using the more "modern" updating API; `verbgraph`,
221 | // which is discussed in the "Updating correctly" section.
222 | for press_event in self.button_press_listener.peek() {
223 | self.on_button_press(press_event);
224 | }
225 | }
226 | ```
227 |
228 | ## Updating correctly
229 |
230 | The above code is fine, but for more a complex UI then there is the possibility of events being processed out-of-order.
231 | To fix this, Reclutch has the `verbgraph` module; a facility to jump between widgets and into their specific queue handlers.
232 | In essence, it breaks the linear execution of update procedures so that dependent events can be handled even if the primary `update` function has already be executed.
233 |
234 | This is best shown through example;
235 |
236 | ```rust
237 | fn new() -> Self {
238 | let graph = verbgraph! {
239 | Self as obj,
240 | Aux as aux,
241 |
242 | // the string "count_up" is the tag used to identify procedures.
243 | // they can also overlap.
244 | "count_up" => event in &count_up.event => {
245 | click => {
246 | // here we mutate a variable that `obj.template_label` implicitly/indirectly depends on.
247 | obj.count += 1;
248 | // Here template_label is assumed to be a label whose text uses a template engine
249 | // that needs to be explicitly rendered.
250 | obj.template_label.values[0] = obj.count.to_string();
251 | // If we don't call this then `obj.dynamic_label` doesn't
252 | // get a chance to respond to our changes in this update pass.
253 | // This doesn't invoke the entire update cycle for `template_label`, only the specific part we care about; `"update_template"`.
254 | reclutch_verbgraph::require_update(&mut obj.template_label, aux, "update_template");
255 | // "update_template" refers to the tag.
256 | }
257 | }
258 | };
259 | // ...
260 | }
261 |
262 | fn update(&mut self, aux: &mut Aux) {
263 | for child in self.children_mut() {
264 | child.update(aux);
265 | }
266 |
267 | reclutch_verbgraph::update_all(self, aux);
268 | }
269 | ```
270 |
271 | In the `verbgraph` module is also the `Event` trait, which is required to support the syntax seen in `verbgraph!`.
272 |
273 | ```rust
274 | #[derive(Event, Clone)]
275 | enum AnEvent {
276 | #[event_key(pop)]
277 | Pop,
278 | #[event_key(squeeze)]
279 | Squeeze(f32),
280 | #[event_key(smash)]
281 | Smash {
282 | force: f64,
283 | hulk: bool,
284 | },
285 | }
286 | ```
287 |
288 | Generates exactly;
289 |
290 | ```rust
291 | impl reclutch::verbgraph::Event for AnEvent {
292 | fn get_key(&self) -> &'static str {
293 | match self {
294 | AnEvent::Pop => "pop",
295 | AnEvent::Squeeze(..) => "squeeze",
296 | AnEvent::Smash{..} => "smash",
297 | }
298 | }
299 | }
300 |
301 | impl AnEvent {
302 | pub fn unwrap_as_pop(self) -> Option<()> {
303 | if let AnEvent::Pop = self {
304 | Some(())
305 | } else {
306 | None
307 | }
308 | }
309 |
310 | pub fn unwrap_as_squeeze(self) -> Option<(f32)> {
311 | if let AnEvent::Squeeze(x0) = self {
312 | Some((x0))
313 | } else {
314 | None
315 | }
316 | }
317 |
318 | pub fn unwrap_as_smash(self) -> Option<(f64, bool)> {
319 | if let AnEvent::Smash{force, hulk} = self {
320 | Some((force, hulk))
321 | } else {
322 | None
323 | }
324 | }
325 | }
326 | ```
327 |
328 | `get_key` is used to find the correct closure to execute given an event
329 | and `unwrap_as_` is used to extract the inner information from within the
330 | given closure (because once `get_key` is matched then we can be certain it
331 | is of a certain variant).
332 |
333 | ## License
334 |
335 | Reclutch is licensed under either
336 |
337 | - [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0)
338 | - [MIT](http://opensource.org/licenses/MIT)
339 |
340 | at your choosing.
341 |
342 | This license also applies to all "sub-projects" (`event`, `derive` and `verbgraph`).
343 |
--------------------------------------------------------------------------------
/core/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "reclutch_core"
3 | version = "0.0.0"
4 | authors = ["jazzfool "]
5 | edition = "2018"
6 | license = "MIT / Apache-2.0"
7 | description = "Core components of Reclutch"
8 | homepage = "http://github.com/jazzfool/reclutch/tree/master/core"
9 | repository = "http://github.com/jazzfool/reclutch"
10 |
11 | [features]
12 | skia = ["skia-safe", "gl", "linked-hash-map"]
13 |
14 | [dependencies]
15 | reclutch_event = { path = "../event" }
16 | euclid = "0.20"
17 | thiserror = "1.0"
18 | font-kit = "0.6"
19 | palette = "0.5"
20 | xi-unicode = "0.2"
21 | skia-safe = { version = "0.27", optional = true, features = ["gl"] }
22 | gl = { version = "0.14", optional = true }
23 | linked-hash-map = { version = "0.5", optional = true }
24 |
25 | [dev-dependencies]
26 | float-cmp = "0.8"
27 |
--------------------------------------------------------------------------------
/core/src/error.rs:
--------------------------------------------------------------------------------
1 | use thiserror::Error;
2 |
3 | /// An error within `font_kit`.
4 | #[derive(Error, Debug)]
5 | pub enum FontError {
6 | #[error("{0}")]
7 | LoadingError(#[from] font_kit::error::FontLoadingError),
8 | #[error("{0}")]
9 | GlyphLoadingError(#[from] font_kit::error::GlyphLoadingError),
10 | #[error("{0}")]
11 | MatchingError(#[from] font_kit::error::SelectionError),
12 | #[error("failed to look up matching codepoint for character")]
13 | CodepointError,
14 | }
15 |
16 | /// An error within Skia and its interactions with OpenGL.
17 | #[derive(Error, Debug)]
18 | #[cfg(feature = "skia")]
19 | pub enum SkiaError {
20 | #[error("the OpenGL target {0} is invalid")]
21 | InvalidTarget(String),
22 | #[error("invalid OpenGL context")]
23 | InvalidContext,
24 | #[error("unknown skia error")]
25 | UnknownError,
26 | }
27 |
28 | /// An error associated with loading graphical resources.
29 | #[derive(Error, Debug)]
30 | pub enum ResourceError {
31 | #[error("{0} is not a file")]
32 | InvalidPath(String),
33 | #[error("{0}")]
34 | IoError(#[from] std::io::Error),
35 | #[error("given resource data is invalid and cannot be read/decoded")]
36 | InvalidData,
37 | #[error("{0}")]
38 | InternalError(#[from] Box),
39 | }
40 |
41 | /// An error related to [`GraphicsDisplay`](crate::display::GraphicsDisplay).
42 | #[derive(Error, Debug)]
43 | pub enum DisplayError {
44 | #[error("{0}")]
45 | ResourceError(#[from] ResourceError),
46 | #[error("non-existent resource reference (id: {0})")]
47 | InvalidResource(u64),
48 | #[error("mismatched resource reference type (id: {0})")]
49 | MismatchedResource(u64),
50 | #[error("{0}")]
51 | InternalError(#[from] Box),
52 | }
53 |
--------------------------------------------------------------------------------
/core/src/lib.rs:
--------------------------------------------------------------------------------
1 | //! Core components of Reclutch, such as the Widget types and the display module.
2 |
3 | pub mod display;
4 | pub mod error;
5 |
6 | pub use euclid;
7 | pub use font_kit;
8 | pub use palette;
9 |
10 | #[cfg(feature = "skia")]
11 | pub use skia_safe as skia;
12 |
13 | #[cfg(feature = "skia")]
14 | pub use gl;
15 |
16 | /// Intricate event queues.
17 | pub use reclutch_event as event;
18 |
19 | pub mod prelude {
20 | pub use crate::{
21 | display::GraphicsDisplay,
22 | widget::{Widget, WidgetChildren},
23 | };
24 | pub use reclutch_event::prelude::*;
25 | }
26 |
27 | /// Widget systems in which Reclutch is built around.
28 | pub mod widget {
29 | use crate::display::{GraphicsDisplay, Rect};
30 |
31 | /// Simple widget trait with a render boundary, event updating and rendering.
32 | pub trait Widget {
33 | type UpdateAux;
34 | type GraphicalAux;
35 | type DisplayObject;
36 |
37 | /// The bounds method doesn't necessarily have an internal need within Reclutch,
38 | /// however widget boundaries is crucial data in every GUI, for things such as
39 | /// layout, partial redraw, and input.
40 | fn bounds(&self) -> Rect {
41 | Rect::default()
42 | }
43 |
44 | /// Perhaps the most important method, this method gives every widget an opportunity
45 | /// to process events, emit events and execute all the side effects attached to such.
46 | /// Event handling is performed through a focused event system (see the event module).
47 | ///
48 | /// This is also where the [`UpdateAux`] associated type comes in.
49 | /// It allows you to pass mutable data around during updating.
50 | ///
51 | /// Here's an example implementation of `update`:
52 | /// ```ignore
53 | /// #[derive(WidgetChildren)]
54 | /// struct Counter { /* fields omitted */ }
55 | ///
56 | /// impl Widget for Counter {
57 | /// type UpdateAux = GlobalData;
58 | /// type GraphicalAux = /* ... */;
59 | /// type DisplayObject = /* ... */;
60 | ///
61 | /// fn update(&mut self, aux: &mut GlobalData) {
62 | /// // propagate to children
63 | /// propagate_update(self, aux);
64 | ///
65 | /// for event in self.count_up_listener.peek() {
66 | /// self.count += 1;
67 | /// self.command_group.repaint();
68 | /// }
69 | ///
70 | /// for event in self.count_down_listener.peek() {
71 | /// self.count -= 1;
72 | /// self.command_group.repaint();
73 | /// }
74 | /// }
75 | ///
76 | /// // --snip--
77 | /// }
78 | /// ```
79 | ///
80 | /// [`UpdateAux`]: Widget::UpdateAux
81 | fn update(&mut self, _aux: &mut Self::UpdateAux) {}
82 |
83 | /// Drawing is renderer-agnostic, however this doesn't mean the API is restrictive.
84 | /// Generally, drawing is performed through [`CommandGroup`].
85 | /// This is also where [`GraphicalAux`] and [`DisplayObject`] come in handy.
86 | ///
87 | /// [`GraphicalAux`] allows you to pass extra data around while rendering,
88 | /// much like [`UpdateAux`]. A use case of this could be, for example,
89 | /// rendering widgets into smaller displays and compositing them into a
90 | /// larger display by attaching the larger display as [`GraphicalAux`].
91 | ///
92 | /// [`DisplayObject`] is simply the type that is used for [`GraphicsDisplay`]
93 | /// (i.e. it's the form in which the widget visually expresses itself).
94 | /// If you're doing regular graphical rendering, then it is strongly
95 | /// advised to use [`DisplayCommand`], which is the type supported by the
96 | /// default rendering back-ends. For more information, see [`GraphicsDisplay`].
97 | ///
98 | /// A simple example of this can be seen below:
99 | /// ```ignore
100 | /// struct MyWidget {
101 | /// cmd_group: CommandGroup,
102 | /// }
103 | ///
104 | /// impl Widget for MyWidget {
105 | /// type GraphicalAux = ();
106 | /// type DisplayObject = DisplayCommand;
107 | ///
108 | /// // --snip--
109 | ///
110 | /// fn draw(&mut self, display: &mut dyn GraphicsDisplay, _aux: &mut ()) {
111 | /// // note that the builder is an optional abstraction which stands in
112 | /// // place of creating an array of DisplayCommands by hand, which can be
113 | /// // cumbersome.
114 | /// let mut builder = DisplayListBuilder::new();
115 | ///
116 | /// // push display items to the builder
117 | ///
118 | /// self.cmd_group.push(display, &builder.build(), None);
119 | /// }
120 | /// }
121 | /// ```
122 | /// Notice that although [`DisplayObject`] is defined as [`DisplayCommand`],
123 | /// it needn't be passed to the `display` parameter's type. This is because
124 | /// [`GraphicsDisplay`] defaults the generic to [`DisplayCommand`] already.
125 | ///
126 | /// [`CommandGroup`]: crate::display::CommandGroup
127 | /// [`GraphicalAux`]: Widget::GraphicalAux
128 | /// [`DisplayObject`]: Widget::DisplayObject
129 | /// [`UpdateAux`]: Widget::UpdateAux
130 | /// [`DisplayCommand`]: crate::display::DisplayCommand
131 | fn draw(
132 | &mut self,
133 | _display: &mut dyn GraphicsDisplay,
134 | _aux: &mut Self::GraphicalAux,
135 | ) {
136 | }
137 | }
138 |
139 | /// Interface to get children of a widget as an array of dynamic widgets.
140 | ///
141 | /// Ideally, this wouldn't be implemented directly, but rather with `derive(WidgetChildren)`.
142 | pub trait WidgetChildren: Widget {
143 | /// Returns all the children as immutable dynamic references.
144 | fn children(
145 | &self,
146 | ) -> Vec<
147 | &dyn WidgetChildren<
148 | UpdateAux = Self::UpdateAux,
149 | GraphicalAux = Self::GraphicalAux,
150 | DisplayObject = Self::DisplayObject,
151 | >,
152 | > {
153 | Vec::new()
154 | }
155 |
156 | /// Returns all the children as mutable dynamic references.
157 | fn children_mut(
158 | &mut self,
159 | ) -> Vec<
160 | &mut dyn WidgetChildren<
161 | UpdateAux = Self::UpdateAux,
162 | GraphicalAux = Self::GraphicalAux,
163 | DisplayObject = Self::DisplayObject,
164 | >,
165 | > {
166 | Vec::new()
167 | }
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/derive/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "reclutch_derive"
3 | version = "0.0.0"
4 | authors = ["jazzfool "]
5 | edition = "2018"
6 | license = "MIT / Apache-2.0"
7 | description = "Utility to make implementing Widget children easier"
8 | homepage = "http://github.com/jazzfool/reclutch/tree/master/derive"
9 | repository = "http://github.com/jazzfool/reclutch"
10 |
11 | [lib]
12 | proc-macro = true
13 |
14 | [dependencies]
15 | syn = "1.0"
16 | quote = "1.0"
17 | proc-macro2 = "1.0"
18 |
--------------------------------------------------------------------------------
/derive/src/event.rs:
--------------------------------------------------------------------------------
1 | use {proc_macro::TokenStream, quote::quote};
2 |
3 | pub fn impl_event_macro(ast: syn::DeriveInput) -> TokenStream {
4 | match ast.data {
5 | syn::Data::Enum(enum_data) => {
6 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
7 | let name = ast.ident;
8 |
9 | let mut key_pats: Vec = Vec::new();
10 | let mut cast_fns: Vec = Vec::new();
11 |
12 | for variant in enum_data.variants {
13 | let key = find_event_key(&variant.attrs);
14 | let um: proc_macro2::TokenStream = get_unmatched_variant(&variant).into();
15 | let func = quote::format_ident!("unwrap_as_{}", key);
16 |
17 | let (match_ext, ty, ret) = get_variant_matched_tuples(&variant);
18 | let (match_ext, ty, ret): (
19 | proc_macro2::TokenStream,
20 | proc_macro2::TokenStream,
21 | proc_macro2::TokenStream,
22 | ) = (match_ext.into(), ty.into(), ret.into());
23 |
24 | key_pats.push({
25 | quote! { #name::#um => std::stringify!(#key) }
26 | });
27 |
28 | cast_fns.push({
29 | quote! {
30 | pub fn #func(self) -> Option<#ty> {
31 | if let #name::#match_ext = self {
32 | Some(#ret)
33 | } else {
34 | None
35 | }
36 | }
37 | }
38 | });
39 | }
40 |
41 | {
42 | quote! {
43 | impl #impl_generics reclutch::verbgraph::Event for #name #ty_generics #where_clause {
44 | fn get_key(&self) -> &'static str {
45 | match self {
46 | #(#key_pats),*
47 | }
48 | }
49 | }
50 |
51 | impl #impl_generics #name #ty_generics #where_clause {
52 | #(#cast_fns)*
53 | }
54 | }
55 | }
56 | .into()
57 | }
58 | syn::Data::Struct(_) => {
59 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
60 | let name = ast.ident;
61 | let key = find_event_key(&ast.attrs);
62 | let func = quote::format_ident!("unwrap_as_{}", key);
63 |
64 | {
65 | quote! {
66 | impl #impl_generics reclutch::verbgraph::Event for #name #ty_generics #where_clause {
67 | fn get_key(&self) -> &'static str {
68 | std::stringify!(#key)
69 | }
70 | }
71 |
72 | impl #impl_generics #name #ty_generics #where_clause {
73 | pub fn #func(self) -> Option {
74 | Some(self)
75 | }
76 | }
77 | }
78 | }
79 | .into()
80 | }
81 | _ => panic!("derive(Event) only supports structs and enums."),
82 | }
83 | }
84 |
85 | fn get_variant_matched_tuples(variant: &syn::Variant) -> (TokenStream, TokenStream, TokenStream) {
86 | let name = &variant.ident;
87 | match &variant.fields {
88 | syn::Fields::Unit => (
89 | {
90 | quote! { #name }
91 | }
92 | .into(),
93 | {
94 | quote! { () }
95 | }
96 | .into(),
97 | {
98 | quote! { () }
99 | }
100 | .into(),
101 | ),
102 | syn::Fields::Unnamed(fields) => {
103 | let (matching, types): (Vec, Vec) = fields
104 | .unnamed
105 | .iter()
106 | .enumerate()
107 | .map(|(idx, field)| {
108 | (quote::format_ident!("x{}", idx.to_string()), field.ty.clone())
109 | })
110 | .unzip();
111 |
112 | (
113 | {
114 | quote! {
115 | #name(#(#matching),*)
116 | }
117 | }
118 | .into(),
119 | {
120 | quote! {
121 | (#(#types),*)
122 | }
123 | }
124 | .into(),
125 | {
126 | quote! {
127 | (#(#matching),*)
128 | }
129 | }
130 | .into(),
131 | )
132 | }
133 | syn::Fields::Named(fields) => {
134 | let mut matching: Vec = Vec::new();
135 | let mut types: Vec = Vec::new();
136 | for field in &fields.named {
137 | matching.push(field.ident.clone().unwrap());
138 | types.push(field.ty.clone());
139 | }
140 |
141 | (
142 | {
143 | quote! {
144 | #name{#(#matching),*}
145 | }
146 | }
147 | .into(),
148 | {
149 | quote! {
150 | (#(#types),*)
151 | }
152 | }
153 | .into(),
154 | {
155 | quote! {
156 | (#(#matching),*)
157 | }
158 | }
159 | .into(),
160 | )
161 | }
162 | }
163 | }
164 |
165 | fn get_unmatched_variant(variant: &syn::Variant) -> TokenStream {
166 | match variant.fields {
167 | syn::Fields::Unit => {
168 | let ident = variant.ident.clone();
169 |
170 | {
171 | quote! {
172 | #ident
173 | }
174 | }
175 | .into()
176 | }
177 | syn::Fields::Unnamed(_) => {
178 | let ident = variant.ident.clone();
179 |
180 | {
181 | quote! {
182 | #ident(..)
183 | }
184 | }
185 | .into()
186 | }
187 | syn::Fields::Named(_) => {
188 | let ident = variant.ident.clone();
189 |
190 | {
191 | quote! {
192 | #ident{..}
193 | }
194 | }
195 | .into()
196 | }
197 | }
198 | }
199 |
200 | fn find_event_key(attrs: &[syn::Attribute]) -> syn::Ident {
201 | for attr in attrs {
202 | if attr.path.segments.first().map(|i| i.ident == "event_key").unwrap_or(false) {
203 | if let proc_macro2::TokenTree::Group(grp) =
204 | attr.clone().tokens.into_iter().next().unwrap()
205 | {
206 | if let proc_macro2::TokenTree::Ident(ident) =
207 | grp.stream().into_iter().next().unwrap()
208 | {
209 | return ident;
210 | }
211 | }
212 | }
213 | }
214 | panic!("Variant missing an event_key")
215 | }
216 |
--------------------------------------------------------------------------------
/derive/src/lib.rs:
--------------------------------------------------------------------------------
1 | extern crate proc_macro;
2 |
3 | mod event;
4 |
5 | use {proc_macro::TokenStream, quote::quote};
6 |
7 | #[proc_macro_derive(
8 | WidgetChildren,
9 | attributes(widget_child, vec_widget_child, widget_children_trait)
10 | )]
11 | pub fn widget_macro_derive(input: TokenStream) -> TokenStream {
12 | let ast = syn::parse(input).unwrap();
13 |
14 | impl_widget_macro(&ast)
15 | }
16 |
17 | enum ChildAttr {
18 | None,
19 | WidgetChild,
20 | VecWidgetChild,
21 | }
22 |
23 | enum StringOrInt {
24 | String(String),
25 | Int(usize),
26 | }
27 |
28 | enum ChildReference {
29 | Single(StringOrInt),
30 | Vec(StringOrInt),
31 | }
32 |
33 | fn chk_attrs_is_child(attrs: &[syn::Attribute]) -> ChildAttr {
34 | for attr in attrs {
35 | if attr.path.segments.first().map(|i| i.ident == "widget_child").unwrap_or(false) {
36 | return ChildAttr::WidgetChild;
37 | } else if attr.path.segments.first().map(|i| i.ident == "vec_widget_child").unwrap_or(false)
38 | {
39 | return ChildAttr::VecWidgetChild;
40 | }
41 | }
42 | ChildAttr::None
43 | }
44 |
45 | fn impl_widget_macro(ast: &syn::DeriveInput) -> TokenStream {
46 | let trait_type = if let Some(attr) = ast.attrs.iter().find(|attr| {
47 | attr.path.segments.first().map(|i| i.ident == "widget_children_trait").unwrap_or(false)
48 | }) {
49 | let mut out = None;
50 | for token in attr.tokens.clone().into_iter() {
51 | if let proc_macro2::TokenTree::Group(grp) = token {
52 | out = Some(grp.stream());
53 | break;
54 | }
55 | }
56 |
57 | out
58 | } else {
59 | None
60 | }
61 | .unwrap_or(quote! { reclutch::widget::WidgetChildren });
62 |
63 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
64 | let name = &ast.ident;
65 | let mut children = Vec::new();
66 |
67 | let mut capacity = 0;
68 | if let syn::Data::Struct(ref data) = &ast.data {
69 | match &data.fields {
70 | syn::Fields::Named(fields) => {
71 | for field in fields.named.iter() {
72 | if let Some(ref ident) = field.ident {
73 | match chk_attrs_is_child(&field.attrs) {
74 | ChildAttr::None => continue,
75 | ChildAttr::WidgetChild => {
76 | capacity += 1;
77 | children.push(ChildReference::Single(StringOrInt::String(
78 | ident.to_string(),
79 | )));
80 | }
81 | ChildAttr::VecWidgetChild => {
82 | children.push(ChildReference::Vec(StringOrInt::String(
83 | ident.to_string(),
84 | )));
85 | }
86 | }
87 | }
88 | }
89 | }
90 | syn::Fields::Unnamed(fields) => {
91 | for (i, field) in fields.unnamed.iter().enumerate() {
92 | match chk_attrs_is_child(&field.attrs) {
93 | ChildAttr::None => continue,
94 | ChildAttr::WidgetChild => {
95 | capacity += 1;
96 | children.push(ChildReference::Single(StringOrInt::Int(i)));
97 | }
98 | ChildAttr::VecWidgetChild => {
99 | children.push(ChildReference::Vec(StringOrInt::Int(i)));
100 | }
101 | }
102 | }
103 | }
104 | _ => (),
105 | }
106 | }
107 |
108 | let mut push_children = Vec::new();
109 | let mut push_children_mut = Vec::new();
110 | let mut capacities = Vec::new();
111 |
112 | for child in children {
113 | match child {
114 | ChildReference::Single(ident) => match ident {
115 | StringOrInt::String(child) => {
116 | let ident = quote::format_ident!("{}", child);
117 | push_children.push(quote! { children.push(&self.#ident as _); });
118 | push_children_mut.push(quote! { children.push(&mut self.#ident as _); });
119 | }
120 | StringOrInt::Int(child) => {
121 | let ident = syn::Index::from(child);
122 | push_children.push(quote! { children.push(&self.#ident as _); });
123 | push_children_mut.push(quote! { children.push(&mut self.#ident as _); });
124 | }
125 | },
126 | ChildReference::Vec(ident) => match ident {
127 | StringOrInt::String(child) => {
128 | let ident = quote::format_ident!("{}", child);
129 | push_children
130 | .push(quote! { for child in &self.#ident { children.push(child as _); } });
131 | push_children_mut.push(
132 | quote! { for child in &mut self.#ident { children.push(child as _); } },
133 | );
134 | capacities.push(quote! { + self.#ident.len() });
135 | }
136 | StringOrInt::Int(child) => {
137 | let ident = syn::Index::from(child);
138 | push_children
139 | .push(quote! { for child in &self.#ident { children.push(child as _); } });
140 | push_children_mut.push(
141 | quote! { for child in &mut self.#ident { children.push(child as _); } },
142 | );
143 | capacities.push(quote! { + self.#ident.len() });
144 | }
145 | },
146 | }
147 | }
148 |
149 | {
150 | quote! {
151 | impl #impl_generics #trait_type for #name #ty_generics #where_clause {
152 | fn children(
153 | &self
154 | ) -> Vec<
155 | &dyn #trait_type<
156 | UpdateAux = Self::UpdateAux,
157 | GraphicalAux = Self::GraphicalAux,
158 | DisplayObject = Self::DisplayObject,
159 | >
160 | > {
161 | let mut children = Vec::with_capacity(#capacity as usize #(#capacities)*);
162 | #(#push_children)*
163 | children
164 | }
165 | fn children_mut(
166 | &mut self
167 | ) -> Vec<
168 | &mut dyn #trait_type<
169 | UpdateAux = Self::UpdateAux,
170 | GraphicalAux = Self::GraphicalAux,
171 | DisplayObject = Self::DisplayObject,
172 | >
173 | > {
174 | let mut children = Vec::with_capacity(#capacity as usize #(#capacities)*);
175 | #(#push_children_mut)*
176 | children
177 | }
178 | }
179 | }
180 | }
181 | .into()
182 | }
183 |
184 | #[proc_macro_derive(OperatesVerbGraph)]
185 | pub fn operates_verb_graph_macro_derive(input: TokenStream) -> TokenStream {
186 | let ast: syn::DeriveInput = syn::parse(input).unwrap();
187 |
188 | impl_operates_verb_graph_macro(ast)
189 | }
190 |
191 | fn impl_operates_verb_graph_macro(ast: syn::DeriveInput) -> TokenStream {
192 | let name = &ast.ident;
193 | let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
194 |
195 | {
196 | quote! {
197 | impl #impl_generics reclutch::verbgraph::OperatesVerbGraph for #name #ty_generics #where_clause {
198 | fn update_all(&mut self, additional: &mut ::UpdateAux) {
199 | reclutch::verbgraph::update_all(self, additional);
200 | }
201 |
202 | fn require_update(&mut self, additional: &mut ::UpdateAux, tag: &'static str) {
203 | reclutch::verbgraph::require_update(self, additional, tag);
204 | }
205 | }
206 | }
207 | }
208 | .into()
209 | }
210 |
211 | #[proc_macro_derive(Event, attributes(event_key))]
212 | pub fn event_macro_derive(input: TokenStream) -> TokenStream {
213 | let ast: syn::DeriveInput = syn::parse(input).unwrap();
214 |
215 | event::impl_event_macro(ast)
216 | }
217 |
--------------------------------------------------------------------------------
/event/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "reclutch_event"
3 | version = "0.0.0"
4 | authors = ["jazzfool ", "Erik Zscheile "]
5 | edition = "2018"
6 | license = "MIT / Apache-2.0"
7 | description = "Thread-safe and non-thread-safe Event abstractions"
8 | homepage = "http://github.com/jazzfool/reclutch/tree/master/event"
9 | repository = "http://github.com/jazzfool/reclutch"
10 |
11 | [package.metadata.docs.rs]
12 | all-features = true
13 | rustdoc-args = ["--cfg", "docs"]
14 |
15 | [features]
16 | futures = ["futures-core"]
17 |
18 | [dependencies]
19 | crossbeam-channel = { version = "0.4", optional = true }
20 | futures-core = { version = "0.3", optional = true }
21 | retain_mut = "0.1"
22 | slotmap = "0.4"
23 | # keep this in sync with the version listed in reclutch/Cargo.toml
24 | winit = { version = "0.20.0-alpha5", optional = true }
25 |
26 | [dev-dependencies]
27 | criterion = "0.3"
28 | crossbeam-utils = "0.7"
29 | futures-executor = "0.3"
30 | futures-util = "0.3"
31 |
32 | [[bench]]
33 | name = "events"
34 | harness = false
35 |
--------------------------------------------------------------------------------
/event/README.md:
--------------------------------------------------------------------------------
1 | # `reclutch_event`
2 |
3 | ## Overview
4 |
5 | ```
6 | ╭─── RawEventQueue
7 | │
8 | │ ╔═════════════════╦════════════════════╦══════════════════╗
9 | │ ║ sending halves ║ forwarders ║ receiving halves ║
10 | │ ╟─────────────────╫────────────────────╫──────────────────╢
11 | │ ║ ╭───────────────╫────────────────────╫────────────╮ ║
12 | │ ║ │ ║ ║ (push_*) ↑ ║
13 | │ ║ │ ║ ╭───── cascade::* ─╫──┬filter───╯ ║
14 | ↕ ║ ↓ ║ ↑ ║ ╰*...─────╯ ║
15 | │ ║ │ ║ │ ║ ║
16 | ╰─╫─┼─ Emitter[Mut]─╫─┴─ if Listable ─>>─╫──┬─ Listen ║
17 | ╠═╪═══════════════╩════════════════════╬══╪═════════════╤═╩═══════════════╗
18 | ║ ├─ nested Emitters... ║ ├─ merge::* │ multiplexing/ ║
19 | ║ │ (broadcasting) ║ │ (muxing) │ broadcasting ║
20 | ╚═╪════════════════════════════════════╩══╪═════════════╧═════════════════╝
21 | ↑ ↓
22 | ╰─ emit(_Event) ╰─ with(_f(&[_Event]))
23 | ```
24 |
25 | ## Callbacks
26 |
27 | At this level, there are no closures when it comes to callbacks. However, the `verbgraph` event queue abstraction does provide closure-based event handling.
28 |
29 | Instead, the event module uses a simple event queue with listener primitives.
30 |
31 | At it's very core, the event system works like this;
32 | - An event queue store a list of events (we'll call this `Vec`) and a dictionary of listeners (we'll call this `Map`).
33 | - `E` is defined to be a generic type; the event type itself.
34 | - `L` is defined to be a listener ID that is locally unique within the event queue (not globally unique; e.g. not unique across event queues).
35 | - `Idx` represents an index in `Vec`. The event at this index holds the latest event the listener (`L`) has "seen".
36 | - As long as the owning scope of some listener `L` has an immutable reference to the associated event queue, then the events can be processed. We will call the events processed `Vec`.
37 | - The contents of `Vec` is defined to be a slice of `Vec`, `[Idx..]`.
38 | - Thus, `Vec` only contains events that have not been seen by `L` yet, or simply, the events past `Idx`.
39 | - Once `Vec` is returned, `Idx` is set to `Vec.len()`.
40 |
41 | This, however, is only the first part of the event system. It can be determined that there is some lower bound, `Idx`, which signifies the lowest common listener index; the index at which all the preceding events have been "seen" by all the listeners. Therefore, a cleanup process that removes all events preceding `Idx` can be implemented.
42 | - For most implementations, the following steps are invoked by a listener going out of scope.
43 | - Let `Vec` be the list of events and `Idx` be the lowest common listener index.
44 | - The slice `[..Idx]` is removed from `Vec`.
45 | - However, recall that `Map` stores an index in `Vec` much like a pointer.
46 | - Consequent to the removal of `[..Idx`], `Vec` has been offset by `-Idx` such that all indices of a given listener `L` have been invalidated.
47 | - To solve this invalidation, each index of all `L` in `Map` is offset by `-Idx`.
48 | - Hence, all events that have been seen by all existing listeners and thereby will never be seen by any listener again (i.e. completely obsolete), are removed.
49 |
50 | This cleanup process is not perfectly efficient, however. The lowest common listener index can easily be "held back" by a stale listener that hasn't peeked new events for a while.
51 |
52 | Thanks to zserik and his excellent contributions, the events have been made more ergonomic to use and now utilize the RAII pattern to automatically cleanup when listeners go out of scope.
53 | This module's API is reaching a stable point and is capable for usage outside of a UI.
54 |
55 | Here's an example of it's usage outside a widget (with manual updating);
56 |
57 | ```rust
58 | let mut event: RcEventQueue = RcEventQueue::new();
59 |
60 | event.emit_owned(10); // no listeners, so this event won't be received by anyone.
61 |
62 | let listener = event.listen();
63 |
64 | event.emit_owned(1);
65 | event.emit_owned(2);
66 |
67 | // here is how listeners respond to events.
68 | for num in listener.peek() {
69 | print!("{} ", num);
70 | } // prints: "1 2 "
71 |
72 | std::mem::drop(listener); // explicitly called to illustrate cleanup; this removes the listener and therefore doesn't hold back the cleanup process.
73 | ```
74 |
75 | ## Advanced features
76 |
77 | For more advanced use cases, this crate has some feature flags, all disabled by default.
78 | * `crossbeam-channel`
79 | * `winit`
80 |
81 | ## `crossbeam-channel` support
82 |
83 | This crate offers a simple non-blocking API by default. But this isn't enough in multi-threaded
84 | scenarios, because often polling/busy-waiting inside of non-main-threads isn't wanted and wastes
85 | resources. Thus, this crate offers an blocking API if the `crossbeam-channel` feature is enabled,
86 | which utilizes `crossbeam-channel` to support "blocking until an event arrives".
87 |
88 | ### Cascades
89 |
90 | Sometimes, it is necessary to route events between multiple threads and event queues.
91 | When the `crossbeam-channel` feature is enabled, this crate offers the `cascade` API,
92 | which supports filtered event forwarding.
93 |
94 | ## `winit` support
95 |
96 | This feature is in particular useful if combined with the `crossbeam-channel` feature,
97 | because it allows the `cascade` API to deliver events back into the main `winit` event queue.
98 |
--------------------------------------------------------------------------------
/event/benches/events.rs:
--------------------------------------------------------------------------------
1 | #[macro_use]
2 | extern crate criterion;
3 |
4 | use criterion::Criterion;
5 | use reclutch_event::*;
6 | use std::mem::drop;
7 |
8 | fn criterion_benchmark(c: &mut Criterion) {
9 | c.bench_function("rcevent-listener-peek", move |b| {
10 | b.iter(|| {
11 | let event = RcEventQueue::new();
12 |
13 | event.emit_owned(0);
14 |
15 | let listener = event.listen();
16 |
17 | event.emit_owned(1);
18 | event.emit_owned(2);
19 | event.emit_owned(3);
20 |
21 | assert_eq!(listener.peek(), &[1, 2, 3]);
22 | })
23 | });
24 |
25 | c.bench_function("rcevent-listener-with", move |b| {
26 | b.iter(|| {
27 | let event = RcEventQueue::new();
28 |
29 | event.emit_owned(0);
30 |
31 | let listener = event.listen();
32 |
33 | event.emit_owned(1);
34 | event.emit_owned(2);
35 | event.emit_owned(3);
36 |
37 | listener.with(|events| {
38 | assert_eq!(events, &[1, 2, 3]);
39 | });
40 | })
41 | });
42 |
43 | c.bench_function("rcevent-cleanup", move |b| {
44 | b.iter(|| {
45 | let event = RcEventQueue::new();
46 |
47 | let listener_1 = event.listen();
48 |
49 | event.emit_owned(10);
50 |
51 | let listener_2 = event.listen();
52 |
53 | event.emit_owned(20);
54 |
55 | assert_eq!(listener_1.peek(), &[10, 20]);
56 | assert_eq!(listener_2.peek(), &[20]);
57 | let empty_peeked: &[i32] = &[];
58 | assert_eq!(listener_2.peek(), empty_peeked);
59 | assert_eq!(listener_2.peek(), empty_peeked);
60 |
61 | for i in [30; 10].iter() {
62 | event.emit_borrowed(i);
63 | }
64 |
65 | assert_eq!(listener_2.peek(), &[30; 10]);
66 |
67 | drop(listener_1);
68 | })
69 | });
70 |
71 | c.bench_function("nonrcevent-listener-peek", move |b| {
72 | b.iter(|| {
73 | let event = NonRcEventQueue::new();
74 |
75 | event.emit_owned(0);
76 |
77 | let listener = NonRcEventListener::new(&event);
78 |
79 | event.emit_owned(1);
80 | event.emit_owned(2);
81 | event.emit_owned(3);
82 |
83 | assert_eq!(listener.peek(), &[1, 2, 3]);
84 | })
85 | });
86 |
87 | c.bench_function("nonrcevent-listener-with", move |b| {
88 | b.iter(|| {
89 | let event = NonRcEventQueue::new();
90 |
91 | event.emit_owned(0);
92 |
93 | let listener = NonRcEventListener::new(&event);
94 |
95 | event.emit_owned(1);
96 | event.emit_owned(2);
97 | event.emit_owned(3);
98 |
99 | listener.with(|events| {
100 | assert_eq!(events, &[1i32, 2i32, 3i32]);
101 | });
102 | })
103 | });
104 |
105 | c.bench_function("nonrcevent-cleanup", move |b| {
106 | b.iter(|| {
107 | let event = NonRcEventQueue::new();
108 |
109 | let listener_1 = NonRcEventListener::new(&event);
110 |
111 | event.emit_owned(10);
112 |
113 | let listener_2 = NonRcEventListener::new(&event);
114 |
115 | event.emit_owned(20);
116 |
117 | assert_eq!(listener_1.peek(), &[10i32, 20i32]);
118 | assert_eq!(listener_2.peek(), &[20i32]);
119 | let empty_peeked: &[i32] = &[];
120 | assert_eq!(listener_2.peek(), empty_peeked);
121 | assert_eq!(listener_2.peek(), empty_peeked);
122 |
123 | for i in [30; 10].iter() {
124 | event.emit_borrowed(i);
125 | }
126 |
127 | assert_eq!(listener_2.peek(), &[30i32; 10]);
128 |
129 | drop(listener_1);
130 | })
131 | });
132 |
133 | c.bench_function("rawevent-pull-with", move |b| {
134 | b.iter(|| {
135 | let mut event = RawEventQueue::new();
136 |
137 | let listener_1 = event.create_listener();
138 |
139 | event.emit_owned(10);
140 |
141 | let listener_2 = event.create_listener();
142 |
143 | event.emit_owned(20);
144 |
145 | event.pull_with(listener_1, |x| assert_eq!(x, &[10i32, 20i32]));
146 | event.pull_with(listener_2, |x| assert_eq!(x, &[20i32]));
147 | let empty_peeked: &[i32] = &[];
148 | event.pull_with(listener_2, |x| assert_eq!(x, empty_peeked));
149 | event.pull_with(listener_2, |x| assert_eq!(x, empty_peeked));
150 |
151 | for _i in 0..10 {
152 | event.emit_owned(30);
153 | }
154 |
155 | event.pull_with(listener_2, |x| assert_eq!(x, &[30i32; 10]));
156 |
157 | event.remove_listener(listener_1);
158 | })
159 | });
160 |
161 | c.bench_function("event-merge-with", move |b| {
162 | b.iter(|| {
163 | use reclutch_event::merge::Merge;
164 | let event1 = RcEventQueue::new();
165 | let event2 = RcEventQueue::new();
166 | let eventls: Vec<_> = vec![event1.listen(), event2.listen()]
167 | .into_iter()
168 | .map(|i| Box::new(i) as Box>)
169 | .collect();
170 |
171 | event1.emit_owned(0);
172 | event2.emit_owned(1);
173 | event1.emit_owned(2);
174 | event2.emit_owned(3);
175 |
176 | eventls.with(|events| {
177 | assert_eq!(events, &[0i32, 2, 1, 3]);
178 | });
179 | })
180 | });
181 |
182 | c.bench_function("event-merge-map", move |b| {
183 | b.iter(|| {
184 | use reclutch_event::merge::Merge;
185 | let event1 = RcEventQueue::new();
186 | let event2 = RcEventQueue::new();
187 | let eventls: Vec<_> = vec![event1.listen(), event2.listen()]
188 | .into_iter()
189 | .map(|i| Box::new(i) as Box>)
190 | .collect();
191 |
192 | event1.emit_owned(0);
193 | event2.emit_owned(1);
194 | event1.emit_owned(2);
195 | event2.emit_owned(3);
196 |
197 | assert_eq!(eventls.map(|&x| x), &[0i32, 2, 1, 3]);
198 | })
199 | });
200 | }
201 |
202 | criterion_group!(benches, criterion_benchmark);
203 | criterion_main!(benches);
204 |
--------------------------------------------------------------------------------
/event/src/bidir.rs:
--------------------------------------------------------------------------------
1 | use crate::traits::{self, EmitResult};
2 | use std::{borrow::Cow, cell::RefCell, collections::VecDeque, rc::Rc};
3 |
4 | struct InnerRef<'parent, Tin, Tout> {
5 | inq: &'parent mut VecDeque,
6 | outq: &'parent mut VecDeque,
7 | }
8 |
9 | /// Non-thread-safe, reference-counted,
10 | /// bidirectional event queue,
11 | /// designed for `1:1` communication,
12 | /// thus, it doesn't support multi-casting.
13 | ///
14 | /// The first type parameter describes the
15 | /// events which the primary peer receives,
16 | /// the second type parameter describes the
17 | /// events which the secondary peer receives.
18 | #[derive(Clone, Debug)]
19 | pub struct Queue(pub(crate) Rc, VecDeque)>>);
20 |
21 | /// The "other" end of the bidirectional [`Queue`](crate::bidir::Queue)
22 | #[derive(Clone, Debug)]
23 | pub struct Secondary(Queue);
24 |
25 | impl Default for Queue {
26 | fn default() -> Self {
27 | Queue(Rc::new(RefCell::new((VecDeque::new(), VecDeque::new()))))
28 | }
29 | }
30 |
31 | impl Queue {
32 | #[inline]
33 | pub fn new() -> Self {
34 | Default::default()
35 | }
36 |
37 | /// This function returns the "other" end of the bidirectional `Queue`
38 | ///
39 | /// NOTE: multiple calls to this method on the same queue
40 | /// return wrapped references to the same [`Secondary`](crate::bidir::Secondary).
41 | #[inline]
42 | pub fn secondary(&self) -> Secondary {
43 | Secondary(Queue(Rc::clone(&self.0)))
44 | }
45 |
46 | fn on_queues_mut(&self, f: F) -> R
47 | where
48 | F: FnOnce(InnerRef) -> R,
49 | {
50 | let inner = &mut *self.0.borrow_mut();
51 | f(InnerRef { inq: &mut inner.0, outq: &mut inner.1 })
52 | }
53 | }
54 |
55 | impl Secondary {
56 | fn on_queues_mut(&self, f: F) -> R
57 | where
58 | F: FnOnce(InnerRef) -> R,
59 | {
60 | let inner = &mut *(self.0).0.borrow_mut();
61 | f(InnerRef { inq: &mut inner.1, outq: &mut inner.0 })
62 | }
63 | }
64 |
65 | macro_rules! impl_queue_part {
66 | ($strucn:ident, $tp1:ident, $tp2:ident, $tin:ident, $tout:ident) => {
67 | impl<$tp1, $tp2> $strucn<$tp1, $tp2> {
68 | /// Function which iterates over the input event queue
69 | /// and optionally schedules items to be put into the
70 | /// outgoing event queue
71 | pub fn bounce(&self, f: F)
72 | where
73 | F: FnMut($tin) -> Option<$tout>,
74 | {
75 | self.on_queues_mut(|x| {
76 | x.outq.extend(std::mem::replace(x.inq, VecDeque::new()).into_iter().flat_map(f))
77 | })
78 | }
79 |
80 | /// This function retrieves the newest event from
81 | /// the event queue and drops the rest.
82 | pub fn retrieve_newest(&self) -> Option<$tin> {
83 | self.on_queues_mut(|x| x.inq.drain(..).last())
84 | }
85 | }
86 |
87 | impl<$tp1, $tp2> traits::QueueInterfaceCommon for $strucn<$tp1, $tp2> {
88 | type Item = $tout;
89 |
90 | #[inline]
91 | fn buffer_is_empty(&self) -> bool {
92 | self.on_queues_mut(|x| x.outq.is_empty())
93 | }
94 | }
95 |
96 | impl<$tin, $tout: Clone> traits::Emitter for $strucn<$tp1, $tp2> {
97 | #[inline]
98 | fn emit<'a>(&self, event: Cow<'a, $tout>) -> EmitResult<'a, $tout> {
99 | self.on_queues_mut(|x| x.outq.push_back(event.into_owned()));
100 | EmitResult::Delivered
101 | }
102 | }
103 |
104 | impl<$tin: Clone, $tout> traits::Listen for $strucn<$tp1, $tp2> {
105 | type Item = $tin;
106 |
107 | #[inline]
108 | fn with(&self, f: F) -> R
109 | where
110 | F: FnOnce(&[Self::Item]) -> R,
111 | {
112 | f(&self.peek()[..])
113 | }
114 |
115 | #[inline]
116 | fn map(&self, f: F) -> Vec
117 | where
118 | F: FnMut(&Self::Item) -> R,
119 | {
120 | self.on_queues_mut(|x| {
121 | std::mem::replace(x.inq, VecDeque::new()).iter().map(f).collect()
122 | })
123 | }
124 |
125 | #[inline]
126 | fn peek(&self) -> Vec {
127 | self.on_queues_mut(|x| {
128 | std::mem::replace(x.inq, VecDeque::new()).into_iter().collect()
129 | })
130 | }
131 |
132 | #[inline]
133 | fn with_n(&self, n: usize, f: F) -> R
134 | where
135 | F: FnOnce(&[Self::Item]) -> R,
136 | {
137 | f(&self.peek_n(n)[..])
138 | }
139 |
140 | #[inline]
141 | fn map_n(&self, n: usize, f: F) -> Vec
142 | where
143 | F: FnMut(&Self::Item) -> R,
144 | {
145 | self.on_queues_mut(|x| {
146 | let n = n.min(x.inq.len());
147 | x.inq.drain(0..n).collect::>().iter().map(f).collect()
148 | })
149 | }
150 |
151 | #[inline]
152 | fn peek_n(&self, n: usize) -> Vec {
153 | self.on_queues_mut(|x| {
154 | let n = n.min(x.inq.len());
155 | x.inq.drain(0..n).collect()
156 | })
157 | }
158 | }
159 | };
160 | }
161 |
162 | impl_queue_part!(Queue, Tp, Ts, Tp, Ts);
163 | impl_queue_part!(Secondary, Tp, Ts, Ts, Tp);
164 |
165 | #[cfg(test)]
166 | mod tests {
167 | use crate::prelude::*;
168 |
169 | #[test]
170 | fn test_bidir_evq() {
171 | let primary = super::Queue::new();
172 | let secondary = primary.secondary();
173 |
174 | primary.emit_owned(1);
175 | assert_eq!(secondary.peek(), &[1]);
176 | primary.emit_owned(2);
177 | primary.emit_owned(3);
178 | assert_eq!(secondary.peek(), &[2, 3]);
179 |
180 | secondary.emit_owned(4);
181 | secondary.emit_owned(5);
182 | secondary.emit_owned(6);
183 |
184 | primary.bounce(|x| Some(x + 1));
185 | assert_eq!(secondary.peek(), &[5, 6, 7]);
186 | }
187 |
188 | #[test]
189 | fn test_n_bidir_evq() {
190 | let primary = super::Queue::new();
191 | let secondary = primary.secondary();
192 |
193 | primary.emit_owned(1);
194 | assert_eq!(secondary.peek(), &[1]);
195 | primary.emit_owned(2);
196 | primary.emit_owned(3);
197 | assert_eq!(secondary.peek_n(1), &[2]);
198 |
199 | secondary.emit_owned(4);
200 | secondary.emit_owned(5);
201 | secondary.emit_owned(6);
202 |
203 | primary.bounce(|x| Some(x + 1));
204 | assert_eq!(secondary.peek_n(2), &[3, 5]);
205 | assert_eq!(secondary.peek_n(2), &[6, 7]);
206 | }
207 | }
208 |
--------------------------------------------------------------------------------
/event/src/bidir_single.rs:
--------------------------------------------------------------------------------
1 | use crate::traits::{self, EmitResult};
2 | use std::{borrow::Cow, cell::RefCell, rc::Rc};
3 |
4 | struct InnerRef<'parent, Tin, Tout> {
5 | inq: &'parent mut Option,
6 | outq: &'parent mut Option,
7 | }
8 |
9 | /// Non-thread-safe, reference-counted,
10 | /// bidirectional event queue,
11 | /// designed for `1:1` communication,
12 | /// thus, it doesn't support multi-casting.
13 | ///
14 | /// The first type parameter describes the
15 | /// events which the primary peer receives,
16 | /// the second type parameter describes the
17 | /// events which the secondary peer receives.
18 | ///
19 | /// This event queue only ever saves **one**
20 | /// event at a time. The next push replaces the
21 | /// previous event.
22 | #[derive(Clone, Debug)]
23 | pub struct Queue(pub(crate) Rc, Option)>>);
24 |
25 | /// The "other" end of the bidirectional [`Queue`](crate::bidir_single::Queue)
26 | #[derive(Clone, Debug)]
27 | pub struct Secondary(Queue);
28 |
29 | impl Default for Queue {
30 | fn default() -> Self {
31 | Queue(Rc::new(RefCell::new((None, None))))
32 | }
33 | }
34 |
35 | impl Queue {
36 | #[inline]
37 | pub fn new() -> Self {
38 | Default::default()
39 | }
40 |
41 | /// This function returns the "other" end of the bidirectional `Queue`
42 | ///
43 | /// NOTE: multiple calls to this method on the same queue
44 | /// return wrapped references to the same [`Secondary`](crate::bidir_single::Secondary).
45 | #[inline]
46 | pub fn secondary(&self) -> Secondary {
47 | Secondary(Queue(Rc::clone(&self.0)))
48 | }
49 |
50 | fn on_queues_mut(&self, f: F) -> R
51 | where
52 | F: FnOnce(InnerRef<'_, Tp, Ts>) -> R,
53 | {
54 | let inner = &mut *self.0.borrow_mut();
55 | f(InnerRef { inq: &mut inner.0, outq: &mut inner.1 })
56 | }
57 | }
58 |
59 | impl Secondary {
60 | fn on_queues_mut(&self, f: F) -> R
61 | where
62 | F: FnOnce(InnerRef<'_, Ts, Tp>) -> R,
63 | {
64 | let inner = &mut *(self.0).0.borrow_mut();
65 | f(InnerRef { inq: &mut inner.1, outq: &mut inner.0 })
66 | }
67 | }
68 |
69 | macro_rules! impl_queue_part {
70 | ($strucn:ident, $tp1:ident, $tp2:ident, $tin:ident, $tout:ident) => {
71 | impl<$tp1, $tp2> $strucn<$tp1, $tp2> {
72 | /// This function iterates over the input event queue
73 | /// and optionally schedules items to be put into the
74 | /// outgoing event queue
75 | #[inline]
76 | pub fn bounce(&self, f: F)
77 | where
78 | F: FnMut($tin) -> Option<$tout>,
79 | {
80 | self.on_queues_mut(|x| {
81 | if let Some(reply) = x.inq.take().and_then(f) {
82 | *x.outq = Some(reply);
83 | }
84 | })
85 | }
86 |
87 | /// This function retrieves the newest event from
88 | /// the event queue and drops the rest.
89 | #[inline]
90 | pub fn retrieve_newest(&self) -> Option<$tin> {
91 | self.on_queues_mut(|x| x.inq.take())
92 | }
93 | }
94 |
95 | impl<$tp1, $tp2> traits::QueueInterfaceCommon for $strucn<$tp1, $tp2> {
96 | type Item = $tout;
97 |
98 | #[inline]
99 | fn buffer_is_empty(&self) -> bool {
100 | self.on_queues_mut(|x| x.outq.is_none())
101 | }
102 | }
103 |
104 | impl<$tin, $tout: Clone> traits::Emitter for $strucn<$tp1, $tp2> {
105 | #[inline]
106 | fn emit<'a>(&self, event: Cow<'a, $tout>) -> EmitResult<'a, $tout> {
107 | self.on_queues_mut(|x| *x.outq = Some(event.into_owned()));
108 | EmitResult::Delivered
109 | }
110 | }
111 |
112 | impl<$tin: Clone, $tout> traits::Listen for $strucn<$tp1, $tp2> {
113 | type Item = $tin;
114 |
115 | #[inline]
116 | fn with(&self, f: F) -> R
117 | where
118 | F: FnOnce(&[Self::Item]) -> R,
119 | {
120 | f(&self.peek()[..])
121 | }
122 |
123 | #[inline]
124 | fn map(&self, f: F) -> Vec
125 | where
126 | F: FnMut(&Self::Item) -> R,
127 | {
128 | self.on_queues_mut(|x| x.inq.take().iter().map(f).collect())
129 | }
130 |
131 | #[inline]
132 | fn peek(&self) -> Vec {
133 | self.on_queues_mut(|x| x.inq.take().into_iter().collect())
134 | }
135 |
136 | #[inline]
137 | fn with_n(&self, n: usize, f: F) -> R
138 | where
139 | F: FnOnce(&[Self::Item]) -> R,
140 | {
141 | f(&self.peek_n(n)[..])
142 | }
143 |
144 | #[inline]
145 | fn map_n(&self, n: usize, f: F) -> Vec
146 | where
147 | F: FnMut(&Self::Item) -> R,
148 | {
149 | if n == 0 {
150 | Vec::new()
151 | } else {
152 | self.on_queues_mut(|x| x.inq.take().iter().map(f).collect())
153 | }
154 | }
155 |
156 | #[inline]
157 | fn peek_n(&self, n: usize) -> Vec {
158 | if n == 0 {
159 | Vec::new()
160 | } else {
161 | self.on_queues_mut(|x| x.inq.take().into_iter().collect())
162 | }
163 | }
164 | }
165 | };
166 | }
167 |
168 | impl_queue_part!(Queue, Tp, Ts, Tp, Ts);
169 | impl_queue_part!(Secondary, Tp, Ts, Ts, Tp);
170 |
171 | #[cfg(test)]
172 | mod tests {
173 | use crate::prelude::*;
174 |
175 | #[test]
176 | fn test_bidir_evq() {
177 | let primary = super::Queue::new();
178 | let secondary = primary.secondary();
179 |
180 | primary.emit_owned(1);
181 | assert_eq!(secondary.peek(), &[1]);
182 | primary.emit_owned(2);
183 | primary.emit_owned(3);
184 | assert_eq!(secondary.peek(), &[3]);
185 |
186 | secondary.emit_owned(4);
187 | secondary.emit_owned(5);
188 | secondary.emit_owned(6);
189 |
190 | primary.bounce(|x| Some(x + 1));
191 | assert_eq!(secondary.peek(), &[7]);
192 | }
193 |
194 | #[test]
195 | fn test_n_bidir_evq() {
196 | let primary = super::Queue::new();
197 | let secondary = primary.secondary();
198 |
199 | primary.emit_owned(1);
200 | assert_eq!(secondary.peek(), &[1]);
201 | primary.emit_owned(2);
202 | primary.emit_owned(3);
203 | assert_eq!(secondary.peek_n(0), &[]);
204 | assert_eq!(secondary.peek_n(3), &[3]);
205 |
206 | secondary.emit_owned(4);
207 | secondary.emit_owned(5);
208 | secondary.emit_owned(6);
209 |
210 | primary.bounce(|x| Some(x + 1));
211 | assert_eq!(secondary.peek_n(3), &[7]);
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/event/src/cascade/mod.rs:
--------------------------------------------------------------------------------
1 | use crate::traits::{Emitter, EmitterExt};
2 | use crossbeam_channel as chan;
3 |
4 | pub mod utils;
5 |
6 | pub trait CascadeTrait: 'static + Send {
7 | /// Register the cascade input `Receiver`.
8 | /// This function must register exactly one `Receiver` and
9 | /// return the index (as returned from [`Select::recv`](crossbeam_channel::Select::recv)).
10 | fn register_input<'a>(&'a self, sel: &mut chan::Select<'a>) -> usize;
11 |
12 | /// Try to forward data incoming via the registered input `Receiver`.
13 | /// This function must call [`oper.recv`](crossbeam_channel::SelectedOperation::recv).
14 | ///
15 | /// # Return values
16 | /// * `Some([])`: nothing to do
17 | /// * `None`: channel closed -> drop this cascade
18 | /// * `Some([...])`: call [`cleanup`](crate::cascade::CascadeTrait::cleanup) later with the data
19 | ///
20 | /// # Design
21 | /// The processing of incoming events is split into `try_run` and `cleanup`
22 | /// to bypass conflicting borrows between `self` and `oper`, because both
23 | /// hold read-only references to the same underlying memory (`Self`).
24 | /// Thus, `self` can't be a mutable reference because it would conflict with `'a`.
25 | fn try_run<'a>(&self, oper: chan::SelectedOperation<'a>) -> Option;
26 |
27 | /// This function is expected to be called with the unwrapped return value of
28 | /// [`try_run`](crate::cascade::CascadeTrait::try_run) if it returned a non-empty value.
29 | fn cleanup(&mut self, clx: utils::CleanupIndices) -> bool;
30 |
31 | /// Returns if the cascade output filter count is null
32 | fn is_outs_empty(&self) -> bool;
33 | }
34 |
35 | pub trait Push: CascadeTrait + Sized {
36 | type Item: Clone + Send + 'static;
37 |
38 | /// Append a cascade output filter.
39 | /// Each event is forwarded (to `ev_out`) if `filter(&event) == true`.
40 | /// Processing of the event stops after the first matching filter.
41 | ///
42 | /// `keep_after_disconnect` specifies the behavior of this `Cascade` item
43 | /// after `ev_out` signals that it won't accept new events.
44 | /// * `true`: the filter is left in the cascade, but will drop matching events
45 | /// instead of forwarding
46 | /// * `false`: the filter is removed from the cascade, which is equivalent to
47 | /// "`filter` which matches no events"
48 | fn push(self, ev_out: O, keep_after_disconnect: bool, filter: F) -> Self
49 | where
50 | O: Emitter- + Send + 'static,
51 | F: Fn(&Self::Item) -> bool + Send + 'static;
52 |
53 | /// This function extends the functionality of [`push`](crate::cascade::Push::push)
54 | /// with the ability to cascade event queues with different
55 | /// underlying types.
56 | ///
57 | /// `filtmap` is expected to either return:
58 | /// * `Ok` to forward the event item to `ev_out`
59 | /// * `Err` to keep it in the cascade chain and continue with the next filter
60 | fn push_map
(self, ev_out: O, keep_after_disconnect: bool, filtmap: F) -> Self
61 | where
62 | R: Clone + Send + 'static,
63 | O: Emitter- + Send + 'static,
64 | F: Fn(Self::Item) -> Result
+ Send + 'static;
65 |
66 | /// Append a cascade output as notification queue.
67 | /// Every event which isn't matched by any preceding filters is cloned and
68 | /// pushed into the specified event output queue.
69 | fn push_notify(self, ev_out: O) -> Self
70 | where
71 | O: Emitter- + Clone + Send + Sync + 'static,
72 | {
73 | // we need a Clone`-able O to perform the automatic cleanup
74 | self.push(ev_out.clone(), false, move |event| ev_out.emit_borrowed(event).was_delivered())
75 | }
76 |
77 | /// Append a cascade output as notification queue.
78 | /// For each event which isn't matched by any preceding filters,
79 | /// an empty tuple `()` is pushed into the output queue.
80 | /// This is useful to wake up threads which aren't listening on the actual,
81 | /// but need to be notified when events pass through the cascade.
82 | /// Important note: The events which triggered the emission of the tokens
83 | /// possibly aren't available yet when the token is consumed.
84 | fn push_notify_via_token
(self, ev_out: O) -> Self
85 | where
86 | O: Emitter- + Clone + Send + Sync + 'static,
87 | {
88 | // we need a `Clone`-able O to perform the automatic cleanup
89 | self.push_map(ev_out.clone(), false, move |event| {
90 | let _ = ev_out.emit_owned(());
91 | Err(event)
92 | })
93 | }
94 |
95 | /// Register a finalization function.
96 | /// The registered function will run after the event is either
97 | /// * dropped or forwarded --> argument will have value `Ok(())`
98 | /// * or completed the cascade with no matching filters
99 | /// --> argument will have value `Err(event)`
100 | /// The order of the events is unspecified.
101 | ///
102 | /// The finalizer gets removed from the cascade if it returns false.
103 | fn set_finalize
(self, f: F) -> Self
104 | where
105 | F: Fn(Result<(), Self::Item>) -> bool + Send + 'static;
106 |
107 | /// Wraps the current instance into a `Box`
108 | fn wrap(self) -> Box {
109 | assert!(!self.is_outs_empty());
110 | Box::new(self)
111 | }
112 | }
113 |
114 | /// This function runs a cascade routing worker.
115 | /// It expected an control channel (first argument, receiving end)
116 | /// and an initial set of cascades (or `Vec::new()`) as arguments.
117 | ///
118 | /// # Example
119 | /// ```rust
120 | /// let (ctrl_tx, ctrl_rx) = crossbeam_channel::bounded(0);
121 | /// let h = std::thread::spawn(
122 | /// move || reclutch_event::cascade::run_worker(ctrl_rx, Vec::new())
123 | /// );
124 | /// // do stuff, e.g. push new cascades via ctrl_tx
125 | /// // teardown
126 | /// std::mem::drop(ctrl_tx);
127 | /// h.join();
128 | /// ```
129 | pub fn run_worker(
130 | ctrl: chan::Receiver>,
131 | mut cascades: Vec>,
132 | ) {
133 | loop {
134 | let mut sel = chan::Select::new();
135 | sel.recv(&ctrl);
136 |
137 | for i in cascades.iter() {
138 | i.register_input(&mut sel);
139 | }
140 |
141 | if let Some((real_idx, clx)) = loop {
142 | let oper = sel.select();
143 | let idx = oper.index();
144 | break if 0 == idx {
145 | match oper.recv(&ctrl) {
146 | Err(_) => {
147 | // stop signal
148 | return;
149 | }
150 | Ok(x) => {
151 | // new cascade
152 | cascades.push(x);
153 | None
154 | }
155 | }
156 | } else {
157 | Some((
158 | idx - 1,
159 | match cascades.get(idx - 1).unwrap().try_run(oper) {
160 | // channel closed
161 | None => utils::CleanupIndices::new(),
162 | // nothing to do
163 | Some(ref x) if x.is_empty() => continue,
164 | // cleanup needed
165 | Some(x) => x,
166 | },
167 | ))
168 | };
169 | } {
170 | // cleanup part
171 | if clx.is_empty() || cascades.get_mut(real_idx).unwrap().cleanup(clx) {
172 | cascades.remove(real_idx);
173 | }
174 | }
175 | }
176 | }
177 |
178 | #[cfg(test)]
179 | mod tests {
180 | use super::*;
181 | use crate::{chans, dchans, traits::*};
182 |
183 | #[test]
184 | fn cascades_chan() {
185 | let ev1 = chans::Queue::new();
186 | let ev2 = chans::Queue::new();
187 | let ev3 = chans::Queue::new();
188 | let (stop_tx, stop_rx) = chan::bounded(0);
189 | let mut cascades = Vec::new();
190 | cascades.push(
191 | ev1.cascade()
192 | .push(ev2.clone(), false, |i| i % 2 == 1)
193 | .push(ev3.clone(), false, |_| true)
194 | .wrap(),
195 | );
196 | crossbeam_utils::thread::scope(move |s| {
197 | s.spawn(move |_| run_worker(stop_rx, cascades));
198 | let sub = ev2.listen_and_subscribe();
199 | let sub2 = ev3.listen_and_subscribe();
200 | ev1.emit_owned(2).into_result().unwrap();
201 | ev1.emit_owned(1).into_result().unwrap();
202 | assert_eq!(sub.notifier.recv(), Ok(()));
203 | assert_eq!(sub.listener.peek(), &[1]);
204 | assert_eq!(sub2.notifier.recv(), Ok(()));
205 | assert_eq!(sub2.listener.peek(), &[2]);
206 | std::mem::drop(stop_tx);
207 | })
208 | .unwrap();
209 | }
210 |
211 | #[test]
212 | fn cascades_dchan() {
213 | let (ev1_tx, ev1_rx) = chan::unbounded();
214 | let (ev2_tx, ev2_rx) = chan::unbounded();
215 | let (ev3_tx, ev3_rx) = chan::unbounded();
216 | let (stop_tx, stop_rx) = chan::bounded(0);
217 | let mut cascades = Vec::new();
218 | cascades.push(
219 | dchans::Cascade::new(ev1_rx)
220 | .push(ev2_tx, false, |i| i % 2 == 1)
221 | .push(ev3_tx, false, |_| true)
222 | .wrap(),
223 | );
224 | crossbeam_utils::thread::scope(move |s| {
225 | s.spawn(move |_| run_worker(stop_rx, cascades));
226 | ev1_tx.emit_owned(2).into_result().unwrap();
227 | ev1_tx.emit_owned(1).into_result().unwrap();
228 | assert_eq!(ev2_rx.recv(), Ok(1));
229 | assert_eq!(ev3_rx.recv(), Ok(2));
230 | std::mem::drop(stop_tx);
231 | })
232 | .unwrap();
233 | }
234 |
235 | #[test]
236 | fn runtime_cascade() {
237 | let (ev1_tx, ev1_rx) = super::utils::unbounded();
238 | let (ev2_tx, ev2_rx) = chan::unbounded();
239 | let (ev3_tx, ev3_rx) = chan::unbounded();
240 | let (ctrl_tx, ctrl_rx) = chan::bounded(0);
241 | crossbeam_utils::thread::scope(move |s| {
242 | s.spawn(move |_| run_worker(ctrl_rx, Vec::new()));
243 | ctrl_tx
244 | .send(
245 | ev1_rx.push(ev2_tx, false, |i| i % 2 == 1).push(ev3_tx, false, |_| true).wrap(),
246 | )
247 | .unwrap();
248 | ev1_tx.emit_owned(2).into_result().unwrap();
249 | ev1_tx.emit_owned(1).into_result().unwrap();
250 | assert_eq!(ev2_rx.recv(), Ok(1));
251 | assert_eq!(ev3_rx.recv(), Ok(2));
252 | })
253 | .unwrap();
254 | }
255 |
256 | #[test]
257 | fn cascade_map() {
258 | let (ev1_tx, ev1_rx) = super::utils::unbounded();
259 | let (ev2_tx, ev2_rx) = chan::unbounded();
260 | let (ev3_tx, ev3_rx) = chan::unbounded();
261 | let (stop_tx, stop_rx) = chan::bounded(0);
262 | let mut cascades = Vec::new();
263 | cascades.push(
264 | ev1_rx.push(ev2_tx, false, |i| i % 2 == 1).push_map(ev3_tx, false, |_| Ok(true)).wrap(),
265 | );
266 | crossbeam_utils::thread::scope(move |s| {
267 | s.spawn(move |_| run_worker(stop_rx, cascades));
268 | ev1_tx.emit_owned(2).into_result().unwrap();
269 | ev1_tx.emit_owned(1).into_result().unwrap();
270 | assert_eq!(ev2_rx.recv(), Ok(1));
271 | assert_eq!(ev3_rx.recv(), Ok(true));
272 | std::mem::drop(stop_tx);
273 | })
274 | .unwrap();
275 | }
276 |
277 | #[test]
278 | fn cascade_internal_routing_low() {
279 | let (ev1_tx, ev1_rx) = super::utils::unbounded();
280 | let (ev2_tx, ev2_rx) = chan::unbounded();
281 | let (evi_tx, evi_rx) = super::utils::unbounded(); // evi_rx is a cascade
282 | let mut cascades = Vec::new();
283 | cascades.push(ev1_rx.push(evi_tx, false, |_| true).wrap());
284 | cascades.push(evi_rx.push(ev2_tx, false, |_| true).wrap());
285 | crossbeam_utils::thread::scope(move |s| {
286 | let (_stop_tx, stop_rx) = chan::bounded(0);
287 | s.spawn(move |_| run_worker(stop_rx, cascades));
288 | ev1_tx.emit_owned(2).into_result().unwrap();
289 | ev1_tx.emit_owned(1).into_result().unwrap();
290 | std::mem::drop(ev1_tx);
291 | assert_eq!(ev2_rx.recv(), Ok(2));
292 | assert_eq!(ev2_rx.recv(), Ok(1));
293 | // the following only works when the 'evi' cascade is dropped
294 | assert_eq!(ev2_rx.recv(), Err(chan::RecvError));
295 | })
296 | .unwrap();
297 | }
298 |
299 | #[test]
300 | fn cascade_internal_routing_high() {
301 | let (ev1_tx, ev1_rx) = super::utils::unbounded();
302 | let (ev2_tx, ev2_rx) = chan::unbounded();
303 | let (evi_tx, evi_rx) = super::utils::unbounded();
304 | let mut cascades = Vec::new();
305 | cascades.push(ev1_rx.push(evi_tx, false, |_| true).wrap());
306 | cascades.push(evi_rx.push(ev2_tx, false, |_| true).wrap());
307 | crossbeam_utils::thread::scope(move |s| {
308 | let (_stop_tx, stop_rx) = chan::bounded(0);
309 | s.spawn(move |_| run_worker(stop_rx, cascades));
310 | ev1_tx.emit_owned(2).into_result().unwrap();
311 | ev1_tx.emit_owned(1).into_result().unwrap();
312 | std::mem::drop(ev1_tx);
313 | assert_eq!(ev2_rx.recv(), Ok(2));
314 | assert_eq!(ev2_rx.recv(), Ok(1));
315 | // the following only works when the 'evi' cascade is dropped
316 | assert_eq!(ev2_rx.recv(), Err(chan::RecvError));
317 | })
318 | .unwrap();
319 | }
320 | }
321 |
--------------------------------------------------------------------------------
/event/src/cascade/utils.rs:
--------------------------------------------------------------------------------
1 | /// maps cascade_out index to
2 | /// * `true`: set destination = black hole
3 | /// * `false`: drop filter
4 | pub type CleanupIndices = std::collections::HashMap;
5 |
6 | pub type FinalizeContainer = Option) -> bool + Send + 'static>>;
7 |
8 | /// This function is a wrapper around the [`crossbeam-channel::unbounded`]
9 | /// function, and returns a `(:Emitter, :CascadeTrait)` tuple
10 | pub fn unbounded(
11 | ) -> (crossbeam_channel::Sender, crate::dchans::Cascade) {
12 | let (tx, rx) = crossbeam_channel::unbounded();
13 | (tx, crate::dchans::Cascade::new(rx))
14 | }
15 |
16 | /// This helper function is designed to be called inside of implementations
17 | /// of [`CascadeTrait::cleanup`](crate::cascade::CascadeTrait::cleanup)
18 | pub fn cleanup(
19 | outs: &mut Vec<(F, bool)>,
20 | finalize: &mut FinalizeContainer,
21 | mut clx: CleanupIndices,
22 | ) -> bool {
23 | if clx.remove(&outs.len()).is_some() {
24 | // the finalizer is invalidated
25 | *finalize = None;
26 | }
27 |
28 | let mut any_active_out = finalize.is_some();
29 | *outs = std::mem::replace(outs, Vec::new())
30 | .into_iter()
31 | .enumerate()
32 | .filter_map(|(n, mut i)| {
33 | match clx.get(&n) {
34 | Some(&false) => return None,
35 | Some(&true) => i.1 = true,
36 | _ => {}
37 | }
38 | any_active_out |= !i.1;
39 | Some(i)
40 | })
41 | .collect();
42 |
43 | any_active_out
44 | }
45 |
--------------------------------------------------------------------------------
/event/src/chans.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | cascade::{utils::CleanupIndices, CascadeTrait},
3 | traits::Emitter,
4 | *,
5 | };
6 | use crossbeam_channel as chan;
7 | use std::sync::{Arc, RwLock};
8 |
9 | #[derive(Debug)]
10 | struct Intern {
11 | ev: RawEventQueue,
12 | subscribers: Vec>,
13 | }
14 |
15 | #[derive(Debug)]
16 | pub struct CombinedListener {
17 | pub listener: Listener,
18 | pub notifier: chan::Receiver<()>,
19 | }
20 |
21 | #[derive(Debug)]
22 | pub struct Queue(Arc>>);
23 |
24 | impl Clone for Queue {
25 | #[inline]
26 | fn clone(&self) -> Self {
27 | Self(self.0.clone())
28 | }
29 | }
30 |
31 | impl Default for Intern {
32 | #[inline]
33 | fn default() -> Self {
34 | Self { ev: Default::default(), subscribers: Default::default() }
35 | }
36 | }
37 |
38 | impl Default for Queue {
39 | #[inline]
40 | fn default() -> Self {
41 | Self(Arc::new(RwLock::new(Default::default())))
42 | }
43 | }
44 |
45 | impl Queue {
46 | #[doc(hidden)]
47 | #[inline]
48 | fn with_inner(&self, f: F) -> R
49 | where
50 | F: FnOnce(&Intern) -> R,
51 | {
52 | let inner = self.0.read().unwrap();
53 | f(&inner)
54 | }
55 |
56 | #[doc(hidden)]
57 | #[inline]
58 | fn with_inner_mut(&self, f: F) -> R
59 | where
60 | F: FnOnce(&mut Intern) -> R,
61 | {
62 | let mut inner = self.0.write().unwrap();
63 | f(&mut inner)
64 | }
65 |
66 | fn create_channel() -> (chan::Sender<()>, chan::Receiver<()>) {
67 | chan::unbounded()
68 | }
69 |
70 | pub fn subscribe(&self) -> chan::Receiver<()> {
71 | let (tx, rx) = Self::create_channel();
72 | self.with_inner_mut(move |inner| inner.subscribers.push(tx));
73 | rx
74 | }
75 |
76 | pub fn listen_and_subscribe(&self) -> CombinedListener {
77 | let (tx, rx) = Self::create_channel();
78 | let id = self.with_inner_mut(move |inner| {
79 | inner.subscribers.push(tx);
80 | inner.ev.create_listener()
81 | });
82 | CombinedListener { listener: Listener(id, self.clone()), notifier: rx }
83 | }
84 |
85 | pub fn cascade(&self) -> Cascade
86 | where
87 | T: Send + 'static,
88 | {
89 | let CombinedListener { listener, notifier } = self.listen_and_subscribe();
90 | Cascade { listener, notifier, finalize: None, outs: Vec::new() }
91 | }
92 | }
93 |
94 | impl Intern {
95 | fn notify(&mut self) {
96 | self.subscribers.retain(|i| {
97 | // try to send token
98 | if let Err(chan::TrySendError::Disconnected(())) = i.try_send(()) {
99 | // channel unsubscribed
100 | false
101 | } else {
102 | // channel works
103 | true
104 | }
105 | });
106 | }
107 | }
108 |
109 | impl crate::traits::QueueInterfaceCommon for Queue {
110 | type Item = T;
111 |
112 | #[inline]
113 | fn buffer_is_empty(&self) -> bool {
114 | self.with_inner(|inner| inner.ev.events.is_empty())
115 | }
116 | }
117 |
118 | impl crate::traits::Emitter for Queue {
119 | #[inline]
120 | fn emit<'a>(&self, event: std::borrow::Cow<'a, T>) -> crate::traits::EmitResult<'a, T> {
121 | self.with_inner_mut(|inner| {
122 | if inner.ev.listeners.is_empty() {
123 | Err(event)
124 | } else {
125 | inner.ev.events.push(event.into_owned());
126 | inner.notify();
127 | Ok(())
128 | }
129 | })
130 | .into()
131 | }
132 | }
133 |
134 | impl QueueInterfaceListable for Queue {
135 | type Listener = Listener;
136 |
137 | #[inline]
138 | fn listen(&self) -> Listener {
139 | Listener::new(self.clone())
140 | }
141 | }
142 |
143 | #[derive(Debug)]
144 | pub struct Listener(ListenerKey, Queue);
145 |
146 | impl EventListen for Listener {
147 | type Item = T;
148 |
149 | #[inline]
150 | fn with(&self, f: F) -> R
151 | where
152 | F: FnOnce(&[Self::Item]) -> R,
153 | {
154 | (self.1).0.write().unwrap().ev.pull_with(self.0, f)
155 | }
156 |
157 | #[inline]
158 | fn with_n(&self, n: usize, f: F) -> R
159 | where
160 | F: FnOnce(&[Self::Item]) -> R,
161 | {
162 | (self.1).0.write().unwrap().ev.pull_n_with(n, self.0, f)
163 | }
164 | }
165 |
166 | impl Drop for Listener {
167 | fn drop(&mut self) {
168 | if let Ok(mut q) = (self.1).0.write() {
169 | q.ev.remove_listener(self.0);
170 | }
171 | }
172 | }
173 |
174 | impl Listener {
175 | fn new(event: Queue) -> Self {
176 | let id = event.0.write().unwrap().ev.create_listener();
177 | Listener(id, event)
178 | }
179 | }
180 |
181 | pub struct Cascade {
182 | listener: Listener,
183 | notifier: chan::Receiver<()>,
184 | finalize: crate::cascade::utils::FinalizeContainer,
185 | outs: Vec<(Box, bool) -> Result<(), bool> + Send + 'static>, bool)>,
186 | }
187 |
188 | impl crate::cascade::Push for Cascade {
189 | type Item = T;
190 |
191 | fn push(mut self, ev_out: O, keep_after_disconnect: bool, filter: F) -> Self
192 | where
193 | O: Emitter- + Send + 'static,
194 | F: Fn(&T) -> bool + Send + 'static,
195 | {
196 | self.outs.push((
197 | Box::new(move |x: &mut Vec
, drop_if_match: bool| -> Result<(), bool> {
198 | let (forward, keep) =
199 | std::mem::replace(x, Vec::new()).into_iter().partition(|x| filter(x));
200 | *x = keep;
201 | if drop_if_match
202 | || forward.into_iter().all(|i| ev_out.emit_owned(i).was_delivered())
203 | {
204 | Ok(())
205 | } else {
206 | Err(keep_after_disconnect)
207 | }
208 | }),
209 | false,
210 | ));
211 | self
212 | }
213 |
214 | fn push_map(mut self, ev_out: O, keep_after_disconnect: bool, filtmap: F) -> Self
215 | where
216 | R: Clone + Send + 'static,
217 | O: Emitter- + Send + 'static,
218 | F: Fn(T) -> Result
+ Send + 'static,
219 | {
220 | self.outs.push((
221 | Box::new(move |x: &mut Vec, drop_if_match: bool| -> Result<(), bool> {
222 | let mut keep = Vec::::new();
223 | let mut forward = Vec::::new();
224 |
225 | for i in std::mem::replace(x, Vec::new()) {
226 | match filtmap(i) {
227 | Ok(x) => forward.push(x),
228 | Err(x) => keep.push(x),
229 | }
230 | }
231 |
232 | *x = keep;
233 |
234 | if drop_if_match
235 | || forward.into_iter().all(|i| ev_out.emit_owned(i).was_delivered())
236 | {
237 | Ok(())
238 | } else {
239 | Err(keep_after_disconnect)
240 | }
241 | }),
242 | false,
243 | ));
244 | self
245 | }
246 |
247 | fn set_finalize(mut self, f: F) -> Self
248 | where
249 | F: Fn(Result<(), T>) -> bool + Send + 'static,
250 | {
251 | self.finalize = Some(Box::new(f));
252 | self
253 | }
254 | }
255 |
256 | impl CascadeTrait for Cascade {
257 | fn register_input<'a>(&'a self, sel: &mut chan::Select<'a>) -> usize {
258 | sel.recv(&self.notifier)
259 | }
260 |
261 | fn try_run<'a>(&self, oper: chan::SelectedOperation<'a>) -> Option {
262 | oper.recv(&self.notifier).ok()?;
263 |
264 | // check this here to make sure that
265 | // we have no race condition between `try_fold` and `strong_count`
266 | let is_last_ref = Arc::strong_count(&(self.listener.1).0) == 1;
267 | let mut clx = CleanupIndices::new();
268 |
269 | let events = self.listener.peek();
270 | let eventcnt = events.len();
271 | let rest = self.outs.iter().enumerate().try_fold(events, |mut x, (n, i)| {
272 | if let Err(y) = (i.0)(&mut x, i.1) {
273 | clx.insert(n, y);
274 | }
275 | if x.is_empty() {
276 | None
277 | } else {
278 | Some(x)
279 | }
280 | });
281 |
282 | if let Some(ref finalize) = &self.finalize {
283 | let restlen = rest.as_ref().map(|x| x.len()).unwrap_or(0);
284 |
285 | // first process all unmatched events
286 | let mut finalize_fail = false;
287 | if let Some(rest) = rest {
288 | finalize_fail = rest
289 | .into_iter()
290 | .try_fold((), |(), i| if finalize(Err(i)) { Some(()) } else { None })
291 | .is_some();
292 | }
293 |
294 | if !finalize_fail {
295 | // then process all matched events
296 | for _i in restlen..eventcnt {
297 | if !finalize(Ok(())) {
298 | finalize_fail = true;
299 | break;
300 | }
301 | }
302 | }
303 | if finalize_fail {
304 | clx.insert(self.outs.len(), false);
305 | }
306 | }
307 |
308 | if is_last_ref {
309 | // we are the only reference to this event
310 | // the event queue is now dead
311 | None
312 | } else {
313 | Some(clx)
314 | }
315 | }
316 |
317 | fn cleanup(&mut self, clx: CleanupIndices) -> bool {
318 | crate::cascade::utils::cleanup(&mut self.outs, &mut self.finalize, clx)
319 | }
320 |
321 | fn is_outs_empty(&self) -> bool {
322 | self.outs.is_empty()
323 | }
324 | }
325 |
326 | #[cfg(test)]
327 | mod tests {
328 | use super::*;
329 | use crate::traits::QueueInterfaceCommon;
330 | use std::time::Duration;
331 |
332 | #[test]
333 | fn test_event_listener() {
334 | let event = Queue::new();
335 |
336 | event.emit_owned(0i32).into_result().unwrap_err();
337 |
338 | let suls = event.listen_and_subscribe();
339 | let data = &[1, 2, 3];
340 | let h = std::thread::spawn(move || {
341 | assert_eq!(suls.notifier.recv(), Ok(()));
342 | assert_eq!(suls.notifier.recv(), Ok(()));
343 | assert_eq!(suls.notifier.recv(), Ok(()));
344 | assert_eq!(suls.listener.peek(), data);
345 | });
346 |
347 | for i in data.into_iter() {
348 | event.emit_borrowed(i).into_result().unwrap();
349 | }
350 | h.join().unwrap();
351 | }
352 |
353 | #[test]
354 | fn test_n_event_listener() {
355 | let event = Queue::new();
356 |
357 | event.emit_owned(0i32).into_result().unwrap_err();
358 |
359 | let suls = event.listen_and_subscribe();
360 | let data = &[1, 2, 3];
361 | let h = std::thread::spawn(move || {
362 | assert_eq!(suls.notifier.recv(), Ok(()));
363 | assert_eq!(suls.notifier.recv(), Ok(()));
364 | assert_eq!(suls.notifier.recv(), Ok(()));
365 | assert_eq!(suls.listener.peek_n(2), &[1, 2]);
366 | });
367 |
368 | for i in data.into_iter() {
369 | event.emit_borrowed(i).into_result().unwrap();
370 | }
371 | h.join().unwrap();
372 | }
373 |
374 | #[test]
375 | fn test_event_cleanup() {
376 | let event = Queue::new();
377 |
378 | let suls1 = event.listen_and_subscribe();
379 |
380 | event.emit_owned(10).into_result().unwrap();
381 |
382 | assert!(!event.buffer_is_empty());
383 |
384 | let suls2 = event.listen_and_subscribe();
385 |
386 | event.emit_owned(20).into_result().unwrap();
387 |
388 | let h1 = std::thread::spawn(move || {
389 | assert_eq!(suls1.notifier.recv(), Ok(()));
390 | assert_eq!(suls1.listener.peek(), &[10, 20]);
391 | });
392 | let h2 = std::thread::spawn(move || {
393 | assert_eq!(suls2.notifier.recv(), Ok(()));
394 | assert_eq!(suls2.listener.peek(), &[20i32]);
395 | assert_eq!(suls2.listener.peek(), &[]);
396 | assert_eq!(suls2.listener.peek(), &[]);
397 | std::thread::sleep(Duration::from_millis(400));
398 | assert_eq!(suls2.notifier.recv(), Ok(()));
399 | assert_eq!(suls2.listener.peek(), &[30i32; 10]);
400 | });
401 |
402 | std::thread::sleep(Duration::from_millis(200));
403 | assert!(event.buffer_is_empty());
404 |
405 | for _i in 0..10 {
406 | event.emit_owned(30).into_result().unwrap();
407 | }
408 |
409 | h1.join().unwrap();
410 | h2.join().unwrap();
411 |
412 | assert!(event.buffer_is_empty());
413 | }
414 |
415 | #[test]
416 | fn multiple_events() {
417 | let event1 = Queue::new();
418 | let event2 = Queue::new();
419 |
420 | let suls1 = event1.listen_and_subscribe();
421 | let suls2 = event2.listen_and_subscribe();
422 |
423 | event1.emit_owned(20).into_result().unwrap();
424 | event2.emit_owned(10).into_result().unwrap();
425 |
426 | chan::select! {
427 | recv(suls1.notifier) -> _msg => {
428 | assert_eq!(suls1.listener.peek(), &[20]);
429 | },
430 | recv(suls2.notifier) -> _msg => {
431 | assert_eq!(suls2.listener.peek(), &[10]);
432 | },
433 | default => panic!(),
434 | }
435 |
436 | chan::select! {
437 | recv(suls1.notifier) -> _msg => {
438 | assert_eq!(suls1.listener.peek(), &[20]);
439 | },
440 | recv(suls2.notifier) -> _msg => {
441 | assert_eq!(suls2.listener.peek(), &[10]);
442 | },
443 | default => panic!(),
444 | }
445 | }
446 | }
447 |
--------------------------------------------------------------------------------
/event/src/dchans.rs:
--------------------------------------------------------------------------------
1 | use crate::{
2 | cascade::{utils::CleanupIndices, CascadeTrait},
3 | traits::{Emitter, EmitterExt},
4 | };
5 | use crossbeam_channel as chan;
6 |
7 | #[derive(Debug)]
8 | enum CascadeResultIntern {
9 | ChangeToBlackhole,
10 | Disconnected,
11 | Forwarded,
12 | Kept(T),
13 | }
14 |
15 | pub struct Cascade {
16 | inp: chan::Receiver,
17 | finalize: crate::cascade::utils::FinalizeContainer,
18 | outs: Vec<(
19 | Box CascadeResultIntern + Send + 'static>,
20 | bool,
21 | )>,
22 | }
23 |
24 | impl Cascade {
25 | pub fn new(inp: chan::Receiver) -> Self {
26 | Self {
27 | inp,
28 | finalize: None,
29 | outs: Vec::new(),
30 | }
31 | }
32 | }
33 |
34 | impl crate::cascade::Push for Cascade {
35 | type Item = T;
36 |
37 | fn push(mut self, ev_out: O, keep_after_disconnect: bool, filter: F) -> Self
38 | where
39 | O: Emitter- + Send + 'static,
40 | F: Fn(&T) -> bool + Send + 'static,
41 | {
42 | self.outs.push((
43 | Box::new(move |x: T, drop_if_match: bool| -> CascadeResultIntern
{
44 | if !filter(&x) {
45 | CascadeResultIntern::Kept(x)
46 | } else if drop_if_match || ev_out.emit_owned(x).was_delivered() {
47 | CascadeResultIntern::Forwarded
48 | } else if keep_after_disconnect {
49 | CascadeResultIntern::ChangeToBlackhole
50 | } else {
51 | CascadeResultIntern::Disconnected
52 | }
53 | }),
54 | false,
55 | ));
56 | self
57 | }
58 |
59 | fn push_map(mut self, ev_out: O, keep_after_disconnect: bool, filtmap: F) -> Self
60 | where
61 | R: Clone + Send + 'static,
62 | O: Emitter- + Send + 'static,
63 | F: Fn(T) -> Result
+ Send + 'static,
64 | {
65 | self.outs.push((
66 | Box::new(move |x: T, drop_if_match: bool| -> CascadeResultIntern {
67 | match filtmap(x).map(|i| drop_if_match || ev_out.emit_owned(i).was_delivered()) {
68 | Err(x) => CascadeResultIntern::Kept(x),
69 | Ok(true) => CascadeResultIntern::Forwarded,
70 | Ok(false) => {
71 | if keep_after_disconnect {
72 | CascadeResultIntern::ChangeToBlackhole
73 | } else {
74 | CascadeResultIntern::Disconnected
75 | }
76 | }
77 | }
78 | }),
79 | false,
80 | ));
81 | self
82 | }
83 |
84 | fn set_finalize(mut self, f: F) -> Self
85 | where
86 | F: Fn(Result<(), T>) -> bool + Send + 'static,
87 | {
88 | self.finalize = Some(Box::new(f));
89 | self
90 | }
91 | }
92 |
93 | impl CascadeTrait for Cascade {
94 | fn register_input<'a>(&'a self, sel: &mut chan::Select<'a>) -> usize {
95 | sel.recv(&self.inp)
96 | }
97 |
98 | fn try_run<'a>(&self, oper: chan::SelectedOperation<'a>) -> Option {
99 | let mut clx = CleanupIndices::new();
100 | let mut finalizer_arg: Result<(), T> = Ok(());
101 |
102 | match self
103 | .outs
104 | .iter()
105 | .enumerate()
106 | .try_fold(oper.recv(&self.inp).ok()?, |x, (n, i)| {
107 | match (i.0)(x, i.1) {
108 | CascadeResultIntern::Kept(y) => Ok(y),
109 | CascadeResultIntern::Forwarded => Err(None),
110 | CascadeResultIntern::ChangeToBlackhole => Err(Some((n, true))),
111 | CascadeResultIntern::Disconnected => Err(Some((n, false))),
112 | }
113 | }) {
114 | Err(Some((n, x))) => {
115 | clx.insert(n, x);
116 | }
117 | Ok(x) => {
118 | finalizer_arg = Err(x);
119 | }
120 | _ => {}
121 | }
122 |
123 | if self
124 | .finalize
125 | .as_ref()
126 | .map(|finalize| finalize(finalizer_arg))
127 | .unwrap_or(false)
128 | {
129 | clx.insert(self.outs.len(), false);
130 | }
131 |
132 | Some(clx)
133 | }
134 |
135 | fn cleanup(&mut self, clx: CleanupIndices) -> bool {
136 | crate::cascade::utils::cleanup(&mut self.outs, &mut self.finalize, clx)
137 | }
138 |
139 | fn is_outs_empty(&self) -> bool {
140 | self.outs.is_empty()
141 | }
142 | }
143 |
144 | #[cfg(test)]
145 | mod tests {
146 | use super::*;
147 | use crate::traits::EmitterMutExt;
148 | use std::time::Duration;
149 |
150 | #[test]
151 | fn test_event_listener() {
152 | let (event, subs) = chan::unbounded();
153 |
154 | let h = std::thread::spawn(move || {
155 | for i in [1, 2, 3].iter().copied() {
156 | assert_eq!(subs.recv(), Ok(i));
157 | }
158 | });
159 |
160 | event.emit_owned(1).into_result().unwrap();
161 | event.emit_owned(2).into_result().unwrap();
162 | event.emit_owned(3).into_result().unwrap();
163 | h.join().unwrap();
164 | }
165 |
166 | #[test]
167 | fn test_event_cleanup() {
168 | let mut event = Vec::new();
169 |
170 | let (sender, subs1) = chan::unbounded();
171 | event.push(sender);
172 |
173 | event.emit_owned(10i32).into_result().unwrap();
174 |
175 | let (sender, subs2) = chan::unbounded();
176 | event.push(sender);
177 |
178 | event.emit_owned(20i32).into_result().unwrap();
179 |
180 | let h1 = std::thread::spawn(move || {
181 | assert_eq!(subs1.recv(), Ok(10i32));
182 | assert_eq!(subs1.recv(), Ok(20i32));
183 | });
184 | let h2 = std::thread::spawn(move || {
185 | assert_eq!(subs2.recv(), Ok(20i32));
186 | std::thread::sleep(Duration::from_millis(400));
187 | for _i in 0..10 {
188 | assert_eq!(subs2.recv(), Ok(30i32));
189 | }
190 | });
191 |
192 | std::thread::sleep(Duration::from_millis(200));
193 |
194 | for _i in 0..10 {
195 | event.emit_owned(30i32).into_result().unwrap();
196 | }
197 |
198 | h1.join().unwrap();
199 | h2.join().unwrap();
200 | }
201 |
202 | #[test]
203 | fn multiple_events() {
204 | let (event1, subs1) = chan::unbounded();
205 | let (event2, subs2) = chan::unbounded();
206 |
207 | event1.emit_owned(20i32).into_result().unwrap();
208 | event2.emit_owned(10i32).into_result().unwrap();
209 |
210 | chan::select! {
211 | recv(subs1) -> msg => {
212 | assert_eq!(msg, Ok(20i32));
213 | },
214 | recv(subs2) -> msg => {
215 | assert_eq!(msg, Ok(10i32));
216 | },
217 | default => panic!(),
218 | }
219 |
220 | chan::select! {
221 | recv(subs1) -> msg => {
222 | assert_eq!(msg, Ok(20i32));
223 | },
224 | recv(subs2) -> msg => {
225 | assert_eq!(msg, Ok(10i32));
226 | },
227 | default => panic!(),
228 | }
229 | }
230 | }
231 |
--------------------------------------------------------------------------------
/event/src/intern.rs:
--------------------------------------------------------------------------------
1 | pub(crate) type ListenerKey = slotmap::DefaultKey;
2 |
3 | /// Non-thread-safe, non-reference-counted API
4 | #[derive(Debug)]
5 | pub struct Queue {
6 | pub(crate) listeners: slotmap::SlotMap,
7 | pub(crate) events: Vec,
8 | }
9 |
10 | impl Default for Queue {
11 | fn default() -> Self {
12 | Self { listeners: Default::default(), events: Vec::new() }
13 | }
14 | }
15 |
16 | impl Queue {
17 | /// Create a new event queue
18 | pub fn new() -> Self {
19 | Default::default()
20 | }
21 |
22 | /// Removes all events that have been already seen by all listeners
23 | fn cleanup(&mut self) {
24 | let min_idx = *self.listeners.values().min().unwrap_or(&0);
25 | if min_idx == 0 {
26 | return;
27 | }
28 |
29 | for idx in self.listeners.values_mut() {
30 | *idx -= min_idx;
31 | }
32 |
33 | self.events.drain(0..min_idx);
34 | }
35 |
36 | /// Creates a subscription
37 | pub fn create_listener(&mut self) -> ListenerKey {
38 | let maxidx = self.events.len();
39 | self.listeners.insert(maxidx)
40 | }
41 |
42 | /// Removes a subscription
43 | pub fn remove_listener(&mut self, key: ListenerKey) {
44 | // oldidx != 0 --> this is not a blocker
45 | if self.listeners.remove(key) == Some(0) {
46 | self.cleanup();
47 | }
48 | }
49 |
50 | /// Get the start index of new events since last `pull`
51 | fn pull(&mut self, key: ListenerKey) -> usize {
52 | let maxidx = self.events.len();
53 | std::mem::replace(self.listeners.get_mut(key).unwrap(), maxidx)
54 | }
55 |
56 | /// Get the start index of new events up to `n` since last `pull`/`pull_n`.
57 | fn pull_n(&mut self, n: usize, key: ListenerKey) -> (usize, usize) {
58 | let idx = self.listeners.get_mut(key).unwrap();
59 | let n = n.min(self.events.len() - *idx);
60 | *idx += n;
61 | (*idx - n, n)
62 | }
63 |
64 | /// Applies a function to the list of new events since last `pull`
65 | #[inline]
66 | pub fn pull_with(&mut self, key: ListenerKey, f: F) -> R
67 | where
68 | F: FnOnce(&[T]) -> R,
69 | {
70 | let idx = self.pull(key);
71 | let ret = f(&self.events[idx..]);
72 | if idx == 0 {
73 | // this was a blocker
74 | self.cleanup();
75 | }
76 | ret
77 | }
78 |
79 | #[inline]
80 | pub fn pull_n_with(&mut self, n: usize, key: ListenerKey, f: F) -> R
81 | where
82 | F: FnOnce(&[T]) -> R,
83 | {
84 | let (idx, n) = self.pull_n(n, key);
85 | let ret = f(&self.events[idx..idx + n]);
86 | if idx == 0 {
87 | // this was a blocker
88 | self.cleanup();
89 | }
90 | ret
91 | }
92 |
93 | /// Get the next event since last `pull`
94 | #[inline]
95 | pub fn peek_get(&self, key: ListenerKey) -> Option<&T> {
96 | self.events.get(*self.listeners.get(key)?)
97 | }
98 |
99 | /// Finish with this peek, go to next event
100 | #[inline]
101 | pub fn peek_finish(&mut self, key: ListenerKey) {
102 | let maxidx = self.events.len();
103 | let was_blocker = self
104 | .listeners
105 | .get_mut(key)
106 | .map(|idx| {
107 | if *idx < maxidx {
108 | // only increment the idx if it is in bounds
109 | *idx += 1;
110 | *idx == 1
111 | } else {
112 | false
113 | }
114 | })
115 | .unwrap_or(false);
116 | if was_blocker {
117 | // this was a blocker
118 | self.cleanup();
119 | }
120 | }
121 |
122 | #[cfg(test)]
123 | #[inline]
124 | pub(crate) fn events_len(&self) -> usize {
125 | self.events.len()
126 | }
127 | }
128 |
129 | impl crate::traits::QueueInterfaceCommon for Queue {
130 | type Item = T;
131 |
132 | #[inline]
133 | fn buffer_is_empty(&self) -> bool {
134 | self.events.is_empty()
135 | }
136 | }
137 |
138 | impl crate::traits::EmitterMut for Queue {
139 | #[inline]
140 | fn emit<'a>(&mut self, event: std::borrow::Cow<'a, T>) -> crate::traits::EmitResult<'a, T> {
141 | if !self.listeners.is_empty() {
142 | self.events.push(event.into_owned());
143 | crate::traits::EmitResult::Delivered
144 | } else {
145 | crate::traits::EmitResult::Undelivered(event)
146 | }
147 | }
148 | }
149 |
150 | impl std::iter::Extend for Queue {
151 | #[inline]
152 | fn extend(&mut self, iter: T)
153 | where
154 | T: IntoIterator- ,
155 | {
156 | if !self.listeners.is_empty() {
157 | self.events.extend(iter)
158 | }
159 | }
160 | }
161 |
162 | #[cfg(test)]
163 | mod tests {
164 | use super::Queue;
165 | use crate::traits::EmitterMutExt;
166 |
167 | #[test]
168 | fn test_event_listener() {
169 | let mut event = Queue::new();
170 |
171 | event.emit_owned(0).into_result().unwrap_err();
172 |
173 | let listener = event.create_listener();
174 |
175 | event.emit_owned(1).into_result().unwrap();
176 | event.emit_owned(2).into_result().unwrap();
177 | event.emit_owned(3).into_result().unwrap();
178 |
179 | event.pull_with(listener, |x| assert_eq!(x, &[1, 2, 3]));
180 |
181 | event.remove_listener(listener);
182 | }
183 |
184 | #[test]
185 | fn test_n_event_listener() {
186 | let mut event = Queue::new();
187 |
188 | event.emit_owned(0).into_result().unwrap_err();
189 |
190 | let listener = event.create_listener();
191 |
192 | event.emit_owned(1).into_result().unwrap();
193 | event.emit_owned(2).into_result().unwrap();
194 | event.emit_owned(3).into_result().unwrap();
195 |
196 | event.pull_n_with(2, listener, |x| assert_eq!(x, &[1, 2]));
197 | event.pull_n_with(2, listener, |x| assert_eq!(x, &[3]));
198 |
199 | event.remove_listener(listener);
200 | }
201 |
202 | #[test]
203 | fn test_event_cleanup() {
204 | let mut event = Queue::new();
205 |
206 | let listener_1 = event.create_listener();
207 |
208 | event.emit_owned(10).into_result().unwrap();
209 |
210 | assert_eq!(event.events_len(), 1);
211 |
212 | let listener_2 = event.create_listener();
213 |
214 | event.emit_owned(20).into_result().unwrap();
215 |
216 | event.pull_with(listener_1, |x| assert_eq!(x, &[10, 20]));
217 | event.pull_with(listener_2, |x| assert_eq!(x, &[20]));
218 | event.pull_with(listener_2, |x| assert_eq!(x, &[]));
219 | event.pull_with(listener_2, |x| assert_eq!(x, &[]));
220 |
221 | assert_eq!(event.events_len(), 0);
222 |
223 | for _i in 0..10 {
224 | event.emit_owned(30).into_result().unwrap();
225 | }
226 |
227 | event.pull_with(listener_2, |x| assert_eq!(x, &[30; 10]));
228 |
229 | event.remove_listener(listener_1);
230 |
231 | assert_eq!(event.events_len(), 0);
232 | }
233 | }
234 |
--------------------------------------------------------------------------------
/event/src/lib.rs:
--------------------------------------------------------------------------------
1 | /*!
2 |
3 | ```text
4 | ╭─── RawEventQueue
5 | │
6 | │ ╔═════════════════╦════════════════════╦══════════════════╗
7 | │ ║ sending halves ║ forwarders ║ receiving halves ║
8 | │ ╟─────────────────╫────────────────────╫──────────────────╢
9 | │ ║ ╭───────────────╫────────────────────╫────────────╮ ║
10 | │ ║ │ ║ ║ (push_*) ↑ ║
11 | │ ║ │ ║ ╭───── cascade::* ─╫──┬filter───╯ ║
12 | ↕ ║ ↓ ║ ↑ ║ ╰*...─────╯ ║
13 | │ ║ │ ║ │ ║ ║
14 | ╰─╫─┼─ Emitter[Mut]─╫─┴─ if Listable ─>>─╫──┬─ Listen ║
15 | ╠═╪═══════════════╩════════════════════╬══╪═════════════╤═╩═══════════════╗
16 | ║ ├─ nested Emitters... ║ ├─ merge::* │ multiplexing/ ║
17 | ║ │ (broadcasting) ║ │ (muxing) │ broadcasting ║
18 | ╚═╪════════════════════════════════════╩══╪═════════════╧═════════════════╝
19 | ↑ ↓
20 | ╰─ emit(_Event) ╰─ with(_f(&[_Event]))
21 | ```
22 |
23 | # `Emitter`'s
24 |
25 | `Emitter`'s (data types which implement the [`EventEmitter`](crate::prelude::EventEmitter) trait)
26 | represent an event queue instance including the event sending capability
27 | and often (when they implement the `QueueInterfaceListable`)
28 | have the ability to construct new `Listener`s.
29 |
30 | # `Listener`'s
31 |
32 | `Listener`'s (data types which implement the `EventListen` trait)
33 | represent the receiving end of an event queue.
34 | A `Listener` should be wrapped inside of an Rc or Arc
35 | if you want multiple references to the same listener
36 |
37 | # `crossbeam-channel` support
38 |
39 | This crate offers a simple non-blocking API by default. But this isn't enough in multi-threaded
40 | scenarios, because often polling/busy-waiting inside of non-main-threads isn't wanted and wastes
41 | resources. Thus, this crate offers an blocking API if the `crossbeam-channel` feature is enabled,
42 | which utilizes `crossbeam-channel` to support "blocking until an event arrives".
43 |
44 | In this documentation, all items marked with `channels` are only available if the
45 | `crossbeam-channel` feature is enabled.
46 |
47 | ## Cascades
48 |
49 | Sometimes, it is necessary to route events between multiple threads and event queues.
50 | When the `crossbeam-channel` feature is enabled, this crate offers the `cascade` API,
51 | which supports filtered event forwarding.
52 |
53 | */
54 |
55 | #![cfg_attr(feature = "docs", feature(doc_cfg))]
56 |
57 | mod intern;
58 | #[macro_use]
59 | mod macros;
60 | mod traits;
61 |
62 | /// Contains an bidirectional `1:1`, non-thread-safe, reference-counted API
63 | pub mod bidir;
64 |
65 | /// Like `bidir`, but each direction can only save one event at a time
66 | pub mod bidir_single;
67 |
68 | channels_api! {
69 | /// Contains a thread-safe event-cascading API based upon the
70 | /// subscribable thread-safe APIs.
71 | pub mod cascade;
72 |
73 | /// Contains the subscribable thread-safe API
74 | /// using tokens sent via crossbeam channels
75 | ///
76 | /// This event queue wrapper is slower than `dchans`,
77 | /// but uses lesser memory.
78 | pub mod chans;
79 |
80 | /// Contains the subscribable thread-safe API
81 | /// using direct clones of T sent via crossbeam channels
82 | ///
83 | /// This event queue wrapper is faster than `chans`,
84 | /// but uses more memory, because event items are cloned
85 | /// before being sent via crossbeam channels.
86 | pub mod dchans;
87 | }
88 |
89 | /// Contains an asynchronous, thread-safe API
90 | /// and wrapper types
91 | #[cfg(feature = "futures")]
92 | #[cfg_attr(feature = "docs", doc(cfg(futures)))]
93 | pub mod streaming;
94 |
95 | /// Contains an Event queue merger
96 | pub mod merge;
97 |
98 | /// Contains the non-thread-safe, non-reference-counted API
99 | pub mod nonrc;
100 |
101 | /// Contains the non-thread-safe, reference-counted API
102 | pub mod nonts;
103 |
104 | /// Contains the thread-safe, reference-counted API
105 | pub mod ts;
106 |
107 | // implementation of traits for 3rd party types
108 | #[doc(hidden)]
109 | pub mod thirdparty;
110 |
111 | /// An event queue which drops any incoming item
112 | /// and is always closed.
113 | pub type BlackHole
= std::marker::PhantomData;
114 |
115 | use intern::ListenerKey;
116 |
117 | /// Exports the most important traits
118 | pub mod prelude {
119 | pub use crate::traits::{
120 | Emitter as EventEmitter, EmitterExt as EventEmitterExt, EmitterMut as EventEmitterMut,
121 | EmitterMutExt as EventEmitterMutExt, Listen as EventListen, QueueInterfaceCommon,
122 | QueueInterfaceListable,
123 | };
124 | }
125 |
126 | pub use {
127 | intern::Queue as RawEventQueue,
128 | nonrc::{Listener as NonRcEventListener, Queue as NonRcEventQueue},
129 | nonts::{Listener as RcEventListener, Queue as RcEventQueue},
130 | prelude::*,
131 | traits::EmitResult,
132 | };
133 |
--------------------------------------------------------------------------------
/event/src/macros.rs:
--------------------------------------------------------------------------------
1 | // source of this idea:
2 | // https://github.com/stjepang/async-std/blob/832c70aa0e5f8b156558c988b2550cc21130a79e/src/utils.rs
3 |
4 | #[doc(hidden)]
5 | #[macro_export]
6 | macro_rules! channels_api {
7 | ($($item:item)*) => {
8 | $(
9 | #[cfg(feature = "crossbeam-channel")]
10 | #[cfg_attr(feature = "docs", doc(cfg(channels)))]
11 | $item
12 | )*
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/event/src/merge.rs:
--------------------------------------------------------------------------------
1 | use crate::traits::Listen;
2 |
3 | pub type Listener = Vec>>;
4 |
5 | /// Merging utility trait to take peeked values and append them, either directly or indirectly, to a [`Vec`](std::vec::Vec).
6 | pub trait Merge {
7 | fn extend_other(&self, o: &mut Vec);
8 | fn indirect_with(&self, f: &mut dyn FnMut(&T));
9 | }
10 |
11 | impl Merge for EL
12 | where
13 | T: Clone,
14 | EL: Listen- ,
15 | {
16 | fn extend_other(&self, o: &mut Vec
) {
17 | self.with(|j| o.extend(j.iter().cloned()));
18 | }
19 | fn indirect_with(&self, f: &mut dyn FnMut(&T)) {
20 | self.with(|j| {
21 | for i in j.iter() {
22 | (*f)(i);
23 | }
24 | });
25 | }
26 | }
27 |
28 | impl Listen for Listener {
29 | type Item = T;
30 |
31 | fn with(&self, f: F) -> R
32 | where
33 | F: FnOnce(&[T]) -> R,
34 | {
35 | let mut events = Vec::::new();
36 | for i in self.iter() {
37 | i.extend_other(&mut events);
38 | }
39 | f(&events[..])
40 | }
41 |
42 | fn map(&self, mut f: F) -> Vec
43 | where
44 | F: FnMut(&T) -> R,
45 | {
46 | let mut ret = Vec::new();
47 | for i in self.iter() {
48 | i.indirect_with(&mut |j| ret.push(f(j)));
49 | }
50 | ret
51 | }
52 |
53 | fn with_n(&self, n: usize, f: F) -> R
54 | where
55 | F: FnOnce(&[Self::Item]) -> R,
56 | {
57 | let mut events = Vec::::new();
58 | for i in self.iter().take(n) {
59 | i.extend_other(&mut events);
60 | }
61 | f(&events[..])
62 | }
63 |
64 | fn map_n(&self, n: usize, mut f: F) -> Vec
65 | where
66 | F: FnMut(&Self::Item) -> R,
67 | {
68 | let mut ret = Vec::new();
69 | for i in self.iter().take(n) {
70 | i.indirect_with(&mut |j| ret.push(f(j)));
71 | }
72 | ret
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/event/src/nonrc.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 | use std::{cell::RefCell, ops::Deref};
3 |
4 | /// The most basic form of event queue (i.e. minimal indirection to the raw queue).
5 | #[derive(Debug)]
6 | pub struct Queue(pub RefCell>);
7 |
8 | impl Queue {
9 | #[inline]
10 | pub fn new() -> Self {
11 | Queue(Default::default())
12 | }
13 |
14 | #[inline]
15 | pub fn listen(&self) -> Listener<'_, T> {
16 | Listener::new(&self.0)
17 | }
18 | }
19 |
20 | impl Default for Queue {
21 | #[inline]
22 | fn default() -> Self {
23 | Queue(Default::default())
24 | }
25 | }
26 |
27 | impl Deref for Queue {
28 | type Target = RefCell>;
29 |
30 | #[inline]
31 | fn deref(&self) -> &RefCell> {
32 | &self.0
33 | }
34 | }
35 |
36 | #[derive(Debug)]
37 | pub struct Listener<'parent, T>(ListenerKey, &'parent RefCell>);
38 |
39 | impl EventListen for Listener<'_, T> {
40 | type Item = T;
41 |
42 | #[inline]
43 | fn with(&self, f: F) -> R
44 | where
45 | F: FnOnce(&[Self::Item]) -> R,
46 | {
47 | self.1.borrow_mut().pull_with(self.0, f)
48 | }
49 |
50 | #[inline]
51 | fn with_n(&self, n: usize, f: F) -> R
52 | where
53 | F: FnOnce(&[Self::Item]) -> R,
54 | {
55 | self.1.borrow_mut().pull_n_with(n, self.0, f)
56 | }
57 | }
58 |
59 | impl Drop for Listener<'_, T> {
60 | #[inline]
61 | fn drop(&mut self) {
62 | self.1.borrow_mut().remove_listener(self.0);
63 | }
64 | }
65 |
66 | impl<'a, T> Listener<'a, T> {
67 | #[inline]
68 | pub fn new(parent: &'a RefCell>) -> Self {
69 | Listener(parent.borrow_mut().create_listener(), parent)
70 | }
71 | }
72 |
73 | #[cfg(test)]
74 | mod tests {
75 | use super::*;
76 | use std::mem::drop;
77 |
78 | #[test]
79 | fn test_event_listener() {
80 | let event = Queue::new();
81 |
82 | event.emit_owned(0i32).into_result().unwrap_err();
83 |
84 | let listener = event.listen();
85 |
86 | event.emit_owned(1i32).into_result().unwrap();
87 | event.emit_owned(2i32).into_result().unwrap();
88 | event.emit_owned(3i32).into_result().unwrap();
89 |
90 | assert_eq!(listener.peek(), &[1, 2, 3]);
91 |
92 | drop(listener);
93 | }
94 |
95 | #[test]
96 | fn test_n_event_listener() {
97 | let event = Queue::new();
98 |
99 | event.emit_owned(0i32).into_result().unwrap_err();
100 |
101 | let listener = event.listen();
102 |
103 | event.emit_owned(1i32).into_result().unwrap();
104 | event.emit_owned(2i32).into_result().unwrap();
105 | event.emit_owned(3i32).into_result().unwrap();
106 |
107 | assert_eq!(listener.peek_n(2), &[1, 2]);
108 | assert_eq!(listener.peek_n(2), &[3]);
109 |
110 | drop(listener);
111 | }
112 |
113 | #[test]
114 | fn test_event_cleanup() {
115 | let event = Queue::new();
116 |
117 | let listener_1 = event.listen();
118 |
119 | event.emit_owned(10i32).into_result().unwrap();
120 |
121 | assert_eq!(event.borrow().events.len(), 1);
122 |
123 | let listener_2 = event.listen();
124 |
125 | event.emit_owned(20i32).into_result().unwrap();
126 |
127 | assert_eq!(listener_1.peek(), &[10i32, 20i32]);
128 | assert_eq!(listener_2.peek(), &[20i32]);
129 | assert_eq!(listener_2.peek(), &[]);
130 | assert_eq!(listener_2.peek(), &[]);
131 |
132 | assert_eq!(event.borrow().events.len(), 0);
133 |
134 | for _i in 0..10 {
135 | event.emit_owned(30i32).into_result().unwrap();
136 | }
137 |
138 | assert_eq!(listener_2.peek(), &[30i32; 10]);
139 |
140 | drop(listener_1);
141 |
142 | assert_eq!(event.borrow().events.len(), 0);
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/event/src/nonts.rs:
--------------------------------------------------------------------------------
1 | use crate::*;
2 | use std::{cell::RefCell, ops::Deref, rc::Rc};
3 |
4 | type Intern = Rc>>;
5 |
6 | /// Non-thread-safe queue; a step above [`nonrc`](crate::nonrc).
7 | #[derive(Debug)]
8 | pub struct Queue(pub Intern);
9 |
10 | impl Queue {
11 | #[inline]
12 | pub fn new() -> Self {
13 | Queue(Default::default())
14 | }
15 | }
16 |
17 | impl