├── .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 | [![Build Status](https://travis-ci.com/jazzfool/reclutch.svg?branch=master)](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 Default for Queue { 18 | #[inline] 19 | fn default() -> Self { 20 | Queue(Default::default()) 21 | } 22 | } 23 | 24 | impl Deref for Queue { 25 | type Target = Intern; 26 | 27 | #[inline] 28 | fn deref(&self) -> &Intern { 29 | &self.0 30 | } 31 | } 32 | 33 | impl QueueInterfaceListable for Intern { 34 | type Listener = Listener; 35 | 36 | #[inline] 37 | fn listen(&self) -> Listener { 38 | Listener::new(self.clone()) 39 | } 40 | } 41 | 42 | #[derive(Debug)] 43 | pub struct Listener(ListenerKey, Intern); 44 | 45 | impl EventListen for Listener { 46 | type Item = T; 47 | 48 | #[inline] 49 | fn with(&self, f: F) -> R 50 | where 51 | F: FnOnce(&[Self::Item]) -> R, 52 | { 53 | self.1.borrow_mut().pull_with(self.0, f) 54 | } 55 | 56 | #[inline] 57 | fn with_n(&self, n: usize, f: F) -> R 58 | where 59 | F: FnOnce(&[Self::Item]) -> R, 60 | { 61 | self.1.borrow_mut().pull_n_with(n, self.0, f) 62 | } 63 | } 64 | 65 | impl Drop for Listener { 66 | fn drop(&mut self) { 67 | self.1.borrow_mut().remove_listener(self.0) 68 | } 69 | } 70 | 71 | impl Listener { 72 | fn new(event: Intern) -> Self { 73 | let id = event.borrow_mut().create_listener(); 74 | Listener(id, event) 75 | } 76 | } 77 | 78 | #[cfg(test)] 79 | mod tests { 80 | use super::*; 81 | use std::mem::drop; 82 | 83 | #[test] 84 | fn test_event_listener() { 85 | let event = Queue::default(); 86 | 87 | event.emit_owned(0i32).into_result().unwrap_err(); 88 | 89 | let listener = event.listen(); 90 | 91 | event.emit_owned(1i32).into_result().unwrap(); 92 | event.emit_owned(2i32).into_result().unwrap(); 93 | event.emit_owned(3i32).into_result().unwrap(); 94 | 95 | assert_eq!(listener.peek(), &[1, 2, 3]); 96 | 97 | drop(listener); 98 | } 99 | 100 | #[test] 101 | fn test_n_event_listener() { 102 | let event = Queue::default(); 103 | 104 | event.emit_owned(0i32).into_result().unwrap_err(); 105 | 106 | let listener = event.listen(); 107 | 108 | event.emit_owned(1i32).into_result().unwrap(); 109 | event.emit_owned(2i32).into_result().unwrap(); 110 | event.emit_owned(3i32).into_result().unwrap(); 111 | 112 | assert_eq!(listener.peek_n(2), &[1, 2]); 113 | assert_eq!(listener.peek_n(2), &[3]); 114 | 115 | drop(listener); 116 | } 117 | 118 | #[test] 119 | fn test_event_cleanup() { 120 | let event = Queue::default(); 121 | 122 | let listener_1 = event.listen(); 123 | 124 | event.emit_owned(10i32).into_result().unwrap(); 125 | 126 | assert_eq!(event.borrow().events.len(), 1); 127 | 128 | let listener_2 = event.listen(); 129 | 130 | event.emit_owned(20i32).into_result().unwrap(); 131 | 132 | assert_eq!(listener_1.peek(), &[10i32, 20i32]); 133 | assert_eq!(listener_2.peek(), &[20i32]); 134 | assert_eq!(listener_2.peek(), &[]); 135 | assert_eq!(listener_2.peek(), &[]); 136 | 137 | assert_eq!(event.borrow().events.len(), 0); 138 | 139 | for _i in 0..10 { 140 | event.emit_owned(30i32).into_result().unwrap(); 141 | } 142 | 143 | assert_eq!(listener_2.peek(), &[30i32; 10]); 144 | 145 | drop(listener_1); 146 | 147 | assert_eq!(event.borrow().events.len(), 0); 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /event/src/streaming/direct.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Debug)] 4 | pub struct Queue { 5 | eq: crate::ts::Queue, 6 | wakers: Arc>>, 7 | } 8 | 9 | #[derive(Debug)] 10 | pub struct Listener { 11 | inner: Arc>, 12 | wakers: std::sync::Weak>>, 13 | } 14 | 15 | #[derive(Debug)] 16 | pub struct IndirectRef(Arc>); 17 | 18 | #[derive(Debug)] 19 | pub struct Ref<'parent, T> { 20 | eq: std::sync::RwLockReadGuard<'parent, crate::RawEventQueue>, 21 | key: crate::intern::ListenerKey, 22 | } 23 | 24 | impl Clone for Queue { 25 | #[inline] 26 | fn clone(&self) -> Self { 27 | Self { eq: Arc::clone(&self.eq), wakers: Arc::clone(&self.wakers) } 28 | } 29 | } 30 | 31 | impl Default for Queue { 32 | #[inline] 33 | fn default() -> Self { 34 | Self { eq: Default::default(), wakers: Arc::new(Mutex::new(Vec::new())) } 35 | } 36 | } 37 | 38 | impl QueueInterfaceCommon for Queue { 39 | type Item = T; 40 | 41 | #[inline] 42 | fn buffer_is_empty(&self) -> bool { 43 | self.eq.buffer_is_empty() 44 | } 45 | } 46 | 47 | impl Emitter for Queue { 48 | fn emit<'a>(&self, event: Cow<'a, T>) -> EmitResult<'a, T> { 49 | let ret = self.eq.emit(event); 50 | wake_all(&self.wakers); 51 | ret 52 | } 53 | } 54 | 55 | impl QueueInterfaceListable for Queue { 56 | type Listener = Listener; 57 | 58 | fn listen(&self) -> Self::Listener { 59 | Listener { inner: Arc::new(self.eq.listen()), wakers: Arc::downgrade(&self.wakers) } 60 | } 61 | } 62 | 63 | impl Listen for Listener { 64 | type Item = T; 65 | 66 | #[inline] 67 | fn with(&self, f: F) -> R 68 | where 69 | F: FnOnce(&[T]) -> R, 70 | { 71 | self.inner.with(f) 72 | } 73 | 74 | #[inline] 75 | fn with_n(&self, n: usize, f: F) -> R 76 | where 77 | F: FnOnce(&[Self::Item]) -> R, 78 | { 79 | self.inner.with_n(n, f) 80 | } 81 | } 82 | 83 | impl Stream for Listener { 84 | type Item = IndirectRef; 85 | 86 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>> { 87 | let this = Pin::into_inner(self); 88 | let inner = &this.inner; 89 | 90 | match inner.eq.read().ok() { 91 | Some(eq) if eq.peek_get(inner.key).is_some() => { 92 | Poll::Ready(Some(IndirectRef(Arc::clone(inner)))) 93 | } 94 | _ => { 95 | if let Some(wakers) = this.wakers.upgrade() { 96 | wakers.lock().unwrap().push(cx.waker().clone()); 97 | Poll::Pending 98 | } else { 99 | Poll::Ready(None) 100 | } 101 | } 102 | } 103 | } 104 | } 105 | 106 | impl IndirectRef { 107 | pub fn lock( 108 | &self, 109 | ) -> Result< 110 | Ref<'_, T>, 111 | std::sync::PoisonError>>, 112 | > { 113 | self.0.eq.read().map(|eq| Ref { eq, key: self.0.key }) 114 | } 115 | } 116 | 117 | impl std::ops::Deref for Ref<'_, T> { 118 | type Target = T; 119 | 120 | #[inline] 121 | fn deref(&self) -> &T { 122 | self.eq.peek_get(self.key).unwrap() 123 | } 124 | } 125 | 126 | impl Drop for IndirectRef { 127 | #[inline] 128 | fn drop(&mut self) { 129 | if let Ok(mut eq) = self.0.eq.write() { 130 | eq.peek_finish(self.0.key); 131 | } 132 | } 133 | } 134 | 135 | #[cfg(test)] 136 | mod tests { 137 | use super::*; 138 | use crate::traits::EmitterExt; 139 | 140 | #[test] 141 | fn futuristic() { 142 | let eq = Queue::new(); 143 | let mut eql = eq.listen(); 144 | let h = std::thread::spawn(move || { 145 | futures_executor::block_on(async { 146 | use futures_util::stream::StreamExt; 147 | let mut tmp = Vec::::new(); 148 | while let Some(dat) = eql.next().await { 149 | tmp.push(*dat.lock().unwrap()); 150 | } 151 | assert_eq!(tmp, [1, 2]); 152 | }) 153 | }); 154 | eq.emit_owned(1u32).into_result().unwrap(); 155 | eq.emit_owned(2).into_result().unwrap(); 156 | std::mem::drop(eq); 157 | h.join().unwrap(); 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /event/src/streaming/mod.rs: -------------------------------------------------------------------------------- 1 | // async future-based API 2 | 3 | use crate::traits::{EmitResult, Emitter, Listen, QueueInterfaceCommon, QueueInterfaceListable}; 4 | use futures_core::{ 5 | stream::Stream, 6 | task::{Context, Waker}, 7 | }; 8 | use std::{ 9 | borrow::Cow, 10 | cell::RefCell, 11 | marker::PhantomData, 12 | pin::Pin, 13 | sync::{Arc, Mutex}, 14 | task::Poll, 15 | }; 16 | 17 | mod direct; 18 | mod wrapper; 19 | 20 | pub use self::{direct::*, wrapper::*}; 21 | 22 | fn wake_all(wakers: &Mutex>) { 23 | let mut lock = wakers.lock().unwrap(); 24 | for i in std::mem::replace(&mut *lock, Vec::new()).into_iter() { 25 | i.wake(); 26 | } 27 | } 28 | 29 | pub struct WakerWrapper { 30 | waker: Option, 31 | _phantom: PhantomData, 32 | } 33 | 34 | impl WakerWrapper { 35 | #[inline] 36 | pub fn new(cx: Context<'_>) -> Self { 37 | Self { waker: Some(cx.waker().clone()), _phantom: PhantomData } 38 | } 39 | 40 | #[inline] 41 | pub fn wake(&mut self) { 42 | if let Some(waker) = self.waker.take() { 43 | waker.wake(); 44 | } 45 | } 46 | } 47 | 48 | impl QueueInterfaceCommon for WakerWrapper { 49 | type Item = T; 50 | } 51 | 52 | impl crate::traits::EmitterMut for WakerWrapper { 53 | #[inline] 54 | fn emit<'a>(&mut self, event: Cow<'a, T>) -> EmitResult<'a, T> { 55 | self.wake(); 56 | EmitResult::Undelivered(event) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /event/src/streaming/wrapper.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | #[derive(Clone, Debug, Default)] 4 | pub struct QueueWrapper { 5 | inner: IQ, 6 | wakers: Arc>>, 7 | } 8 | 9 | pub struct ListenerWrapper { 10 | inner: IL, 11 | buf: RefCell::Item>>, 12 | wakers: std::sync::Weak>>, 13 | } 14 | 15 | impl QueueWrapper { 16 | pub fn new(inner: IQ) -> Self { 17 | Self { inner, wakers: Arc::new(Mutex::new(Vec::new())) } 18 | } 19 | 20 | pub fn into_inner(self) -> IQ { 21 | let QueueWrapper { inner, wakers } = self; 22 | wake_all(&wakers); 23 | inner 24 | } 25 | 26 | /// # Safety 27 | /// This method is marked unsafe because it might allow 28 | /// type contract-breaking behavior. 29 | /// It is forbidden to call [`emit`](crate::traits::Emitter::emit) 30 | /// or similar functions on the returned reference. 31 | pub unsafe fn inner_mut(&mut self) -> &mut IQ { 32 | &mut self.inner 33 | } 34 | } 35 | 36 | impl QueueInterfaceCommon for QueueWrapper { 37 | type Item = ::Item; 38 | 39 | fn buffer_is_empty(&self) -> bool { 40 | self.inner.buffer_is_empty() 41 | } 42 | } 43 | 44 | impl Emitter for QueueWrapper 45 | where 46 | IQ: Emitter, 47 | ::Item: Clone, 48 | { 49 | fn emit<'a>(&self, event: Cow<'a, Self::Item>) -> EmitResult<'a, Self::Item> { 50 | let ret = self.inner.emit(event); 51 | wake_all(&self.wakers); 52 | ret 53 | } 54 | } 55 | 56 | impl QueueInterfaceListable for QueueWrapper 57 | where 58 | IQ: QueueInterfaceListable, 59 | ::Item: Clone + Unpin, 60 | { 61 | type Listener = ListenerWrapper<::Listener>; 62 | 63 | fn listen(&self) -> Self::Listener { 64 | ListenerWrapper { 65 | inner: self.inner.listen(), 66 | buf: RefCell::new(Vec::new()), 67 | wakers: Arc::downgrade(&self.wakers), 68 | } 69 | } 70 | } 71 | 72 | impl Listen for ListenerWrapper 73 | where 74 | IL: Listen, 75 | ::Item: Clone, 76 | { 77 | type Item = ::Item; 78 | 79 | fn with(&self, f: F) -> R 80 | where 81 | F: FnOnce(&[Self::Item]) -> R, 82 | { 83 | // TODO: optimize this 84 | let mut buf = self.buf.borrow_mut(); 85 | buf.extend(self.inner.peek().into_iter()); 86 | let ret = f(&buf[..]); 87 | buf.clear(); 88 | ret 89 | } 90 | 91 | fn with_n(&self, n: usize, f: F) -> R 92 | where 93 | F: FnOnce(&[Self::Item]) -> R, 94 | { 95 | let mut buf = self.buf.borrow_mut(); 96 | buf.extend(self.inner.peek().into_iter().take(n)); 97 | let ret = f(&buf[..]); 98 | buf.clear(); 99 | ret 100 | } 101 | } 102 | 103 | impl Stream for ListenerWrapper 104 | where 105 | IL: Listen + Unpin, 106 | ::Item: Clone + Unpin, 107 | { 108 | type Item = ::Item; 109 | 110 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 111 | // TODO: check if pinning projections might be appropriate 112 | // (to get rid of the 'IL: Unpin' requirement) 113 | let this = Pin::into_inner(self); 114 | let buf = this.buf.get_mut(); 115 | 116 | // fetch new elements if needed 117 | if buf.is_empty() { 118 | *buf = this.inner.peek(); 119 | } 120 | 121 | if !buf.is_empty() { 122 | return Poll::Ready(Some(buf.remove(0))); 123 | } 124 | match this.wakers.upgrade() { 125 | None => Poll::Ready(None), 126 | Some(wakers) => { 127 | wakers.lock().unwrap().push(cx.waker().clone()); 128 | Poll::Pending 129 | } 130 | } 131 | } 132 | 133 | fn size_hint(&self) -> (usize, Option) { 134 | (self.buf.borrow().len(), None) 135 | } 136 | } 137 | 138 | #[cfg(test)] 139 | mod tests { 140 | use super::*; 141 | use crate::traits::EmitterExt; 142 | 143 | #[test] 144 | fn futuristic() { 145 | let eq = QueueWrapper::new(crate::ts::Queue::default()); 146 | let mut eql = eq.listen(); 147 | let h = std::thread::spawn(move || { 148 | futures_executor::block_on(async { 149 | use futures_util::stream::StreamExt; 150 | let mut tmp = Vec::::new(); 151 | while let Some(dat) = eql.next().await { 152 | tmp.push(dat); 153 | } 154 | assert_eq!(tmp, [1, 2]); 155 | }) 156 | }); 157 | eq.emit_owned(1u32).into_result().unwrap(); 158 | eq.emit_owned(2).into_result().unwrap(); 159 | std::mem::drop(eq); 160 | h.join().unwrap(); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /event/src/thirdparty.rs: -------------------------------------------------------------------------------- 1 | //! If you can, you should probably use `crossbeam_channel` instead 2 | //! of `std::sync::mpsc`, because it's faster. 3 | //! (enable the support for `crossbeam-channel` via the feature flag) 4 | 5 | use crate::{ 6 | channels_api, 7 | traits::{EmitResult, Emitter, EmitterMut, EmitterMutExt, QueueInterfaceCommon}, 8 | }; 9 | use retain_mut::RetainMut; 10 | use std::{ 11 | borrow::Cow, 12 | cell::RefCell, 13 | ops::{Deref, DerefMut}, 14 | sync::{mpsc, Arc, RwLock}, 15 | }; 16 | 17 | impl EmitterMut for Q 18 | where 19 | Q: Emitter, 20 | Self::Item: Clone, 21 | { 22 | #[inline(always)] 23 | fn emit<'a>(&mut self, event: Cow<'a, Self::Item>) -> EmitResult<'a, Self::Item> { 24 | Emitter::emit(&*self, event) 25 | } 26 | } 27 | 28 | impl QueueInterfaceCommon for std::marker::PhantomData { 29 | type Item = T; 30 | } 31 | 32 | impl Emitter for std::marker::PhantomData { 33 | #[inline(always)] 34 | fn emit<'a>(&self, event: Cow<'a, Self::Item>) -> EmitResult<'a, Self::Item> { 35 | EmitResult::Undelivered(event) 36 | } 37 | } 38 | 39 | impl QueueInterfaceCommon for [Q] { 40 | type Item = ::Item; 41 | 42 | #[inline] 43 | fn buffer_is_empty(&self) -> bool { 44 | self.iter().all(|i| i.buffer_is_empty()) 45 | } 46 | } 47 | 48 | impl EmitterMut for [Q] 49 | where 50 | Q: EmitterMut, 51 | Self::Item: Clone, 52 | { 53 | fn emit<'a>(&mut self, event: Cow<'a, Self::Item>) -> EmitResult<'a, Self::Item> { 54 | if self.len() == 1 { 55 | self.first_mut().unwrap().emit(event) 56 | } else if self.iter_mut().any(|i| i.emit_borrowed(&*event).was_delivered()) { 57 | EmitResult::Delivered 58 | } else { 59 | EmitResult::Undelivered(event) 60 | } 61 | } 62 | } 63 | 64 | impl QueueInterfaceCommon for Vec { 65 | type Item = ::Item; 66 | 67 | #[inline] 68 | fn buffer_is_empty(&self) -> bool { 69 | self.iter().all(|i| i.buffer_is_empty()) 70 | } 71 | } 72 | 73 | impl EmitterMut for Vec 74 | where 75 | Q: EmitterMut, 76 | Self::Item: Clone, 77 | { 78 | fn emit<'a>(&mut self, event: Cow<'a, Self::Item>) -> EmitResult<'a, Self::Item> { 79 | self.retain_mut(|i| i.emit_borrowed(&*event).was_delivered()); 80 | if self.is_empty() { 81 | EmitResult::Undelivered(event) 82 | } else { 83 | EmitResult::Delivered 84 | } 85 | } 86 | } 87 | 88 | impl QueueInterfaceCommon for Box> { 89 | type Item = T; 90 | 91 | #[inline] 92 | fn buffer_is_empty(&self) -> bool { 93 | self.deref().buffer_is_empty() 94 | } 95 | } 96 | 97 | impl EmitterMut for Box> { 98 | #[inline] 99 | fn emit<'a>(&mut self, event: Cow<'a, Self::Item>) -> EmitResult<'a, Self::Item> { 100 | self.deref_mut().emit(event) 101 | } 102 | } 103 | 104 | impl QueueInterfaceCommon for std::rc::Rc { 105 | type Item = ::Item; 106 | 107 | #[inline(always)] 108 | fn buffer_is_empty(&self) -> bool { 109 | self.deref().buffer_is_empty() 110 | } 111 | } 112 | 113 | impl Emitter for std::rc::Rc 114 | where 115 | Q: Emitter, 116 | Self::Item: Clone, 117 | { 118 | #[inline(always)] 119 | fn emit<'a>(&self, event: Cow<'a, Self::Item>) -> EmitResult<'a, Self::Item> { 120 | self.deref().emit(event) 121 | } 122 | } 123 | 124 | impl QueueInterfaceCommon for Arc { 125 | type Item = ::Item; 126 | 127 | #[inline(always)] 128 | fn buffer_is_empty(&self) -> bool { 129 | self.deref().buffer_is_empty() 130 | } 131 | } 132 | 133 | impl Emitter for Arc 134 | where 135 | Q: Emitter, 136 | Self::Item: Clone, 137 | { 138 | #[inline(always)] 139 | fn emit<'a>(&self, event: Cow<'a, Self::Item>) -> EmitResult<'a, Self::Item> { 140 | self.deref().emit(event) 141 | } 142 | } 143 | 144 | impl QueueInterfaceCommon for RefCell { 145 | type Item = ::Item; 146 | 147 | #[inline] 148 | fn buffer_is_empty(&self) -> bool { 149 | self.borrow().buffer_is_empty() 150 | } 151 | } 152 | 153 | impl Emitter for RefCell 154 | where 155 | Q: EmitterMut, 156 | Self::Item: Clone, 157 | { 158 | #[inline] 159 | fn emit<'a>(&self, event: Cow<'a, Self::Item>) -> EmitResult<'a, Self::Item> { 160 | self.borrow_mut().emit(event) 161 | } 162 | } 163 | 164 | impl QueueInterfaceCommon for RwLock { 165 | type Item = ::Item; 166 | 167 | #[inline] 168 | fn buffer_is_empty(&self) -> bool { 169 | self.read().map(|i| i.buffer_is_empty()).unwrap_or(true) 170 | } 171 | } 172 | 173 | impl Emitter for RwLock 174 | where 175 | Q: EmitterMut, 176 | Self::Item: Clone, 177 | { 178 | #[inline] 179 | fn emit<'a>(&self, event: Cow<'a, Self::Item>) -> EmitResult<'a, Self::Item> { 180 | if let Ok(mut i) = self.write() { 181 | i.emit(event) 182 | } else { 183 | EmitResult::Undelivered(event) 184 | } 185 | } 186 | } 187 | 188 | impl QueueInterfaceCommon for mpsc::Sender { 189 | type Item = T; 190 | } 191 | 192 | impl Emitter for mpsc::Sender { 193 | #[inline] 194 | fn emit<'a>(&self, event: Cow<'a, T>) -> EmitResult<'a, T> { 195 | self.send(event.into_owned()).map_err(|mpsc::SendError(x)| Cow::Owned(x)).into() 196 | } 197 | } 198 | 199 | impl QueueInterfaceCommon for mpsc::SyncSender { 200 | type Item = T; 201 | } 202 | 203 | impl Emitter for mpsc::SyncSender { 204 | #[inline] 205 | fn emit<'a>(&self, event: Cow<'a, T>) -> EmitResult<'a, T> { 206 | self.send(event.into_owned()).map_err(|mpsc::SendError(x)| Cow::Owned(x)).into() 207 | } 208 | } 209 | 210 | channels_api! { 211 | impl QueueInterfaceCommon for crossbeam_channel::Sender { 212 | type Item = T; 213 | 214 | #[inline] 215 | fn buffer_is_empty(&self) -> bool { 216 | crossbeam_channel::Sender::is_empty(self) 217 | } 218 | } 219 | 220 | impl Emitter for crossbeam_channel::Sender { 221 | #[inline] 222 | fn emit<'a>(&self, event: Cow<'a, T>) -> EmitResult<'a, T> { 223 | self.send(event.into_owned()).map_err(|crossbeam_channel::SendError(x)| Cow::Owned(x)).into() 224 | } 225 | } 226 | } 227 | 228 | #[cfg(feature = "winit")] 229 | impl QueueInterfaceCommon for winit::event_loop::EventLoopProxy { 230 | type Item = T; 231 | } 232 | 233 | #[cfg(feature = "winit")] 234 | impl Emitter for winit::event_loop::EventLoopProxy { 235 | #[inline] 236 | fn emit<'a>(&self, event: Cow<'a, T>) -> EmitResult<'a, T> { 237 | self.send_event(event.into_owned()) 238 | .map_err(|winit::event_loop::EventLoopClosed(x)| Cow::Owned(x)) 239 | .into() 240 | } 241 | } 242 | 243 | #[cfg(test)] 244 | mod tests { 245 | use crate::traits::EmitterMutExt; 246 | use std::{sync::mpsc, time::Duration}; 247 | 248 | #[test] 249 | fn test_event_listener() { 250 | let mut event = Vec::new(); 251 | 252 | event.emit_owned(0i32).into_result().unwrap_err(); 253 | 254 | let (sender, receiver) = mpsc::channel(); 255 | event.push(sender); 256 | 257 | let data = &[1, 2, 3]; 258 | 259 | let h = std::thread::spawn(move || { 260 | for i in data { 261 | assert_eq!(receiver.recv(), Ok(*i)); 262 | } 263 | }); 264 | 265 | for i in data { 266 | event.emit_borrowed(i).into_result().unwrap(); 267 | } 268 | h.join().unwrap(); 269 | } 270 | 271 | #[test] 272 | fn test_event_cleanup() { 273 | let mut event = Vec::new(); 274 | 275 | let (sender, subs1) = mpsc::channel(); 276 | event.push(sender); 277 | 278 | event.emit_owned(10i32).into_result().unwrap(); 279 | 280 | let (sender, subs2) = mpsc::channel(); 281 | event.push(sender); 282 | 283 | event.emit_owned(20i32).into_result().unwrap(); 284 | 285 | let h1 = std::thread::spawn(move || { 286 | assert_eq!(subs1.recv(), Ok(10i32)); 287 | assert_eq!(subs1.recv(), Ok(20i32)); 288 | }); 289 | let h2 = std::thread::spawn(move || { 290 | assert_eq!(subs2.recv(), Ok(20i32)); 291 | std::thread::sleep(Duration::from_millis(400)); 292 | for _i in 0..10 { 293 | assert_eq!(subs2.recv(), Ok(30i32)); 294 | } 295 | }); 296 | 297 | std::thread::sleep(Duration::from_millis(200)); 298 | 299 | for _i in 0..10 { 300 | event.emit_owned(30i32).into_result().unwrap(); 301 | } 302 | 303 | h1.join().unwrap(); 304 | h2.join().unwrap(); 305 | } 306 | 307 | #[cfg(all(feature = "crossbeam-channel", feature = "winit"))] 308 | #[allow(dead_code)] 309 | fn winit_cascade() { 310 | use crate::cascade::Push; 311 | let (_tx, casc) = crate::cascade::utils::unbounded(); 312 | let eloop = winit::event_loop::EventLoop::::with_user_event(); 313 | let proxy = eloop.create_proxy(); 314 | casc.push(proxy, false, |_| true); 315 | } 316 | } 317 | -------------------------------------------------------------------------------- /event/src/traits.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | 3 | /// `EmitResult` indicates the success or failure of an `event emit`. 4 | /// * `Delivered` means the event was emitted with possible listeners present. 5 | /// * `Undelivered` means replaced with `()` along the way 6 | /// and contains the unconsumed `Cow` argument. 7 | /// Take note that some emit methods might return an always owned 8 | /// event instead. 9 | #[derive(Debug, Clone)] 10 | pub enum EmitResult<'a, T: Clone> { 11 | Delivered, 12 | Undelivered(Cow<'a, T>), 13 | } 14 | 15 | impl<'a, T: Clone> EmitResult<'a, T> { 16 | /// Returns true if the result is `Delivered`, otherwise false. 17 | pub fn was_delivered(&self) -> bool { 18 | match self { 19 | EmitResult::Delivered => true, 20 | EmitResult::Undelivered(_) => false, 21 | } 22 | } 23 | 24 | /// Returns true if the result is `Undelivered`, otherwise false. 25 | pub fn was_undelivered(&self) -> bool { 26 | match self { 27 | EmitResult::Delivered => false, 28 | EmitResult::Undelivered(_) => true, 29 | } 30 | } 31 | 32 | /// Converts this `EmitResult` into [`std::result::Result`]. 33 | pub fn into_result(self) -> Result<(), Cow<'a, T>> { 34 | self.into() 35 | } 36 | } 37 | 38 | impl<'a, T: Clone> From>> for EmitResult<'a, T> { 39 | fn from(result: Result<(), Cow<'a, T>>) -> Self { 40 | match result { 41 | Result::Ok(_) => EmitResult::Delivered, 42 | Result::Err(x) => EmitResult::Undelivered(x), 43 | } 44 | } 45 | } 46 | 47 | impl<'a, T: Clone> From> for Result<(), Cow<'a, T>> { 48 | fn from(emitres: EmitResult<'a, T>) -> Result<(), Cow<'a, T>> { 49 | match emitres { 50 | EmitResult::Delivered => Result::Ok(()), 51 | EmitResult::Undelivered(x) => Result::Err(x), 52 | } 53 | } 54 | } 55 | 56 | pub trait QueueInterfaceCommon { 57 | type Item; 58 | 59 | /// Checks if any events are currently buffered 60 | /// 61 | /// # Return value 62 | /// * `false` if events are currently buffered 63 | /// * `true` if no events are currently buffered or the event queue doesn't 64 | /// support querying this information 65 | fn buffer_is_empty(&self) -> bool { 66 | true 67 | } 68 | } 69 | 70 | /// Every supported event queue implements this trait, mutable variant 71 | /// 72 | /// More types implement this trait (including all types which implement 73 | /// [`Emitter`]), but can't be fully accessed via `Rc`/`Arc`. 74 | /// 75 | /// This trait should be used as a trait bound if possible 76 | /// (instead of the non-mut variant). 77 | pub trait EmitterMut: QueueInterfaceCommon 78 | where 79 | Self::Item: Clone, 80 | { 81 | /// Pushes/emits an event 82 | fn emit<'a>(&mut self, event: Cow<'a, Self::Item>) -> EmitResult<'a, Self::Item>; 83 | } 84 | 85 | /// Every supported indirect event queue implements this trait 86 | /// 87 | /// One should always prefer implementing `Emitter` over 88 | /// [`EmitterMut`] because this trait allows access via `Rc`/`Arc` 89 | /// and because implementing `Emitter` automatically 90 | /// provides one with a implementation of `EmitterMut` thanks 91 | /// to the provided blanket implementation. 92 | pub trait Emitter: QueueInterfaceCommon 93 | where 94 | Self::Item: Clone, 95 | { 96 | /// Pushes/emits an event 97 | fn emit<'a>(&self, event: Cow<'a, Self::Item>) -> EmitResult<'a, Self::Item>; 98 | } 99 | 100 | /// Event queues with the ability to create new listeners implement this trait 101 | pub trait QueueInterfaceListable: QueueInterfaceCommon 102 | where 103 | Self::Item: Clone, 104 | { 105 | type Listener: Listen; 106 | 107 | /// Creates a new event 108 | fn new() -> Self 109 | where 110 | Self: Default, 111 | { 112 | Default::default() 113 | } 114 | 115 | /// Returns a handle to a new listener 116 | fn listen(&self) -> Self::Listener; 117 | } 118 | 119 | pub trait EmitterMutExt: EmitterMut 120 | where 121 | Self::Item: Clone, 122 | { 123 | /// Pushs/emits an event, performing conversion from owned 124 | #[inline] 125 | fn emit_owned(&mut self, event: Self::Item) -> EmitResult<'static, Self::Item> { 126 | self.emit(Cow::Owned(event)) 127 | } 128 | 129 | /// Pushes/emits an event, performing conversion from borrowed 130 | #[inline] 131 | fn emit_borrowed<'a>(&mut self, event: &'a Self::Item) -> EmitResult<'a, Self::Item> { 132 | self.emit(Cow::Borrowed(event)) 133 | } 134 | } 135 | 136 | impl EmitterMutExt for Q where Self::Item: Clone {} 137 | 138 | pub trait EmitterExt: Emitter 139 | where 140 | Self::Item: Clone, 141 | { 142 | /// Pushs/emits an event, performing conversion from owned (convenience method) 143 | #[inline] 144 | fn emit_owned(&self, event: Self::Item) -> EmitResult<'static, Self::Item> { 145 | self.emit(Cow::Owned(event)) 146 | } 147 | 148 | /// Pushs/emits an event, performing conversion from borrowed (convenience method) 149 | #[inline] 150 | fn emit_borrowed<'a>(&self, event: &'a Self::Item) -> EmitResult<'a, Self::Item> { 151 | self.emit(Cow::Borrowed(event)) 152 | } 153 | } 154 | 155 | impl EmitterExt for Q where Self::Item: Clone {} 156 | 157 | pub trait Listen { 158 | type Item; 159 | 160 | /// Applies a function to the list of new events since last `with` or `peek` 161 | /// without cloning T 162 | /// 163 | /// This function is faster than calling [`peek`](Listen::peek) 164 | /// and iterating over the result. 165 | /// 166 | /// It holds a lock on the event while called, which means that recursive 167 | /// calls to [`QueueInterfaceListable`] methods aren't allowed and will deadlock or panic. 168 | fn with(&self, f: F) -> R 169 | where 170 | F: FnOnce(&[Self::Item]) -> R; 171 | 172 | /// Applies a function to each new event since last `with` or `peek` 173 | /// without cloning T 174 | /// 175 | /// This function is sometimes faster than calling [`with`](Listen::with). 176 | /// 177 | /// It holds a lock on the event while called, which means that recursive 178 | /// calls to [`QueueInterfaceListable`] methods aren't allowed and will deadlock or panic. 179 | #[inline] 180 | fn map(&self, mut f: F) -> Vec 181 | where 182 | F: FnMut(&Self::Item) -> R, 183 | { 184 | self.with(|slc| slc.iter().map(|i| f(i)).collect()) 185 | } 186 | 187 | /// Returns a list of new events since last `peek` 188 | #[inline] 189 | fn peek(&self) -> Vec 190 | where 191 | Self::Item: Clone, 192 | { 193 | self.with(<[Self::Item]>::to_vec) 194 | } 195 | 196 | /// Almost identical to [`with`](Listen::peek), however only the first `n` events 197 | /// are given. 198 | fn with_n(&self, n: usize, f: F) -> R 199 | where 200 | F: FnOnce(&[Self::Item]) -> R; 201 | 202 | /// Almost identical to [`map`](Listen::map), however only the first `n` events 203 | /// are given. 204 | #[inline] 205 | fn map_n(&self, n: usize, mut f: F) -> Vec 206 | where 207 | F: FnMut(&Self::Item) -> R, 208 | { 209 | self.with_n(n, |slc| slc.iter().map(|i| f(i)).collect()) 210 | } 211 | 212 | /// Almost identical to [`peek`](Listen::peek), however only the first `n` events 213 | /// are given. 214 | #[inline] 215 | fn peek_n(&self, n: usize) -> Vec 216 | where 217 | Self::Item: Clone, 218 | { 219 | self.with_n(n, <[Self::Item]>::to_vec) 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /event/src/ts.rs: -------------------------------------------------------------------------------- 1 | use crate::*; 2 | use std::sync::{Arc, RwLock}; 3 | 4 | /// Identical to [`nonts`], except using thread-safe abstractions (`Arc` over `Rc` and `RwLock` over `RefCell`). 5 | pub type Queue = Arc>>; 6 | 7 | impl QueueInterfaceListable for Queue { 8 | type Listener = Listener; 9 | 10 | #[inline] 11 | fn listen(&self) -> Listener { 12 | Listener::new(Arc::clone(&self)) 13 | } 14 | } 15 | 16 | #[derive(Debug)] 17 | pub struct Listener { 18 | pub(crate) key: ListenerKey, 19 | pub(crate) eq: Arc>>, 20 | } 21 | 22 | impl EventListen for Listener { 23 | type Item = T; 24 | 25 | #[inline] 26 | fn with(&self, f: F) -> R 27 | where 28 | F: FnOnce(&[Self::Item]) -> R, 29 | { 30 | self.eq.write().ok().unwrap().pull_with(self.key, f) 31 | } 32 | 33 | #[inline] 34 | fn with_n(&self, n: usize, f: F) -> R 35 | where 36 | F: FnOnce(&[Self::Item]) -> R, 37 | { 38 | self.eq.write().ok().unwrap().pull_n_with(n, self.key, f) 39 | } 40 | } 41 | 42 | impl Drop for Listener { 43 | fn drop(&mut self) { 44 | if let Ok(mut eq) = self.eq.write() { 45 | eq.remove_listener(self.key); 46 | } 47 | } 48 | } 49 | 50 | impl Listener { 51 | pub(crate) fn new(eq: Arc>>) -> Self { 52 | let key = eq.write().unwrap().create_listener(); 53 | Listener { key, eq } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /reclutch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "reclutch" 3 | version = "0.0.0" 4 | authors = ["jazzfool "] 5 | edition = "2018" 6 | license = "MIT / Apache-2.0" 7 | description = "Rust UI Core" 8 | homepage = "http://github.com/jazzfool/reclutch/tree/master/core" 9 | repository = "http://github.com/jazzfool/reclutch" 10 | 11 | [features] 12 | default = ["reclutch_derive"] 13 | skia = ["reclutch_core/skia"] 14 | 15 | [dependencies] 16 | reclutch_core = { path = "../core" } 17 | reclutch_verbgraph = { path = "../verbgraph" } 18 | reclutch_derive = { path = "../derive", optional = true } 19 | 20 | [[example]] 21 | name = "counter" 22 | required-features = ["skia"] 23 | 24 | [[example]] 25 | name = "image_viewer" 26 | required-features = ["skia"] 27 | 28 | [[example]] 29 | name = "opengl" 30 | required-features = ["skia"] 31 | 32 | [[example]] 33 | name = "shaping" 34 | required-features = ["skia"] 35 | 36 | [dev-dependencies] 37 | glium = "0.27" 38 | nalgebra = "0.21" 39 | harfbuzz_rs = "1.1" 40 | rusttype = "0.9" 41 | -------------------------------------------------------------------------------- /reclutch/examples/counter/main.rs: -------------------------------------------------------------------------------- 1 | // The classic counter GUI. 2 | 3 | use { 4 | glium::glutin::{ 5 | self, 6 | event::{Event as WinitEvent, WindowEvent}, 7 | event_loop::{ControlFlow, EventLoop}, 8 | }, 9 | reclutch::{ 10 | display::{ 11 | self, Color, CommandGroup, DisplayCommand, DisplayListBuilder, FontInfo, 12 | GraphicsDisplay, GraphicsDisplayPaint, Point, Rect, ResourceData, ResourceDescriptor, 13 | ResourceReference, SharedData, Size, TextDisplayItem, 14 | }, 15 | event::{RcEventListener, RcEventQueue}, 16 | gl, 17 | prelude::*, 18 | WidgetChildren, 19 | }, 20 | }; 21 | 22 | #[derive(Debug, Clone, Copy)] 23 | enum GlobalEvent { 24 | Click(Point), 25 | MouseMove(Point), 26 | } 27 | 28 | #[derive(WidgetChildren)] 29 | struct Counter { 30 | count: i32, 31 | 32 | #[widget_child] 33 | button_increase: Button, 34 | #[widget_child] 35 | button_decrease: Button, 36 | button_increase_press_listener: RcEventListener, 37 | button_decrease_press_listener: RcEventListener, 38 | command_group: CommandGroup, 39 | font_info: FontInfo, 40 | font: Option, 41 | } 42 | 43 | impl Counter { 44 | pub fn new(global: &mut RcEventQueue) -> Self { 45 | let button_increase = Button::new(String::from("Count Up"), Point::new(10.0, 40.0), global); 46 | let button_decrease = 47 | Button::new(String::from("Count Down"), Point::new(10.0, 100.0), global); 48 | let button_increase_press_listener = button_increase.press_event.listen(); 49 | let button_decrease_press_listener = button_decrease.press_event.listen(); 50 | 51 | Self { 52 | count: 0, 53 | button_increase, 54 | button_decrease, 55 | button_increase_press_listener, 56 | button_decrease_press_listener, 57 | command_group: CommandGroup::new(), 58 | font_info: FontInfo::from_name( 59 | "Arial", 60 | &["Helvetica", "Segoe UI", "Lucida Grande"], 61 | None, 62 | ) 63 | .unwrap(), 64 | font: None, 65 | } 66 | } 67 | } 68 | 69 | impl Widget for Counter { 70 | type UpdateAux = (); 71 | type GraphicalAux = (); 72 | type DisplayObject = DisplayCommand; 73 | 74 | fn bounds(&self) -> Rect { 75 | Rect::new(Point::new(0.0, 0.0), Size::new(100.0, 100.0)) 76 | } 77 | 78 | fn update(&mut self, aux: &mut ()) { 79 | for child in self.children_mut() { 80 | child.update(aux); 81 | } 82 | 83 | for _event in self.button_increase_press_listener.peek() { 84 | self.count += 1; 85 | self.command_group.repaint(); 86 | } 87 | 88 | for _event in self.button_decrease_press_listener.peek() { 89 | self.count -= 1; 90 | self.command_group.repaint(); 91 | } 92 | } 93 | 94 | fn draw(&mut self, display: &mut dyn GraphicsDisplay, aux: &mut ()) { 95 | if self.font.is_none() { 96 | self.font = display 97 | .new_resource(ResourceDescriptor::Font(ResourceData::Data(SharedData::RefCount( 98 | std::sync::Arc::new(self.font_info.data().unwrap()), 99 | )))) 100 | .ok(); 101 | } 102 | 103 | let bounds = self.bounds(); 104 | 105 | let mut builder = DisplayListBuilder::new(); 106 | 107 | builder.push_clear(Color::new(1.0, 1.0, 1.0, 1.0)); 108 | 109 | builder.push_text( 110 | TextDisplayItem { 111 | text: format!("Count: {}", self.count).into(), 112 | font: self.font.as_ref().unwrap().clone(), 113 | font_info: self.font_info.clone(), 114 | size: 23.0, 115 | bottom_left: bounds.origin.add_size(&Size::new(10.0, 22.0)), 116 | color: Color::new(0.0, 0.0, 0.0, 1.0).into(), 117 | }, 118 | None, 119 | ); 120 | 121 | self.command_group 122 | .push(display, &builder.build(), display::ZOrder(-1), None, None) 123 | .unwrap(); 124 | 125 | for child in self.children_mut() { 126 | child.draw(display, aux); 127 | } 128 | } 129 | } 130 | 131 | #[derive(WidgetChildren)] 132 | struct Button { 133 | pub press_event: RcEventQueue, 134 | 135 | pub text: String, 136 | pub position: Point, 137 | 138 | hover: bool, 139 | global_listener: RcEventListener, 140 | command_group: CommandGroup, 141 | font_info: FontInfo, 142 | font: Option, 143 | } 144 | 145 | impl Button { 146 | pub fn new(text: String, position: Point, global: &mut RcEventQueue) -> Self { 147 | Self { 148 | press_event: RcEventQueue::default(), 149 | text, 150 | position, 151 | hover: false, 152 | global_listener: global.listen(), 153 | command_group: CommandGroup::new(), 154 | font_info: FontInfo::from_name( 155 | "Arial", 156 | &["Helvetica", "Segoe UI", "Lucida Grande"], 157 | None, 158 | ) 159 | .unwrap(), 160 | font: None, 161 | } 162 | } 163 | } 164 | 165 | impl Widget for Button { 166 | type UpdateAux = (); 167 | type GraphicalAux = (); 168 | type DisplayObject = DisplayCommand; 169 | 170 | fn bounds(&self) -> Rect { 171 | Rect::new(self.position, Size::new(150.0, 50.0)) 172 | } 173 | 174 | fn update(&mut self, _aux: &mut ()) { 175 | let bounds = self.bounds(); 176 | 177 | for event in self.global_listener.peek() { 178 | match event { 179 | GlobalEvent::Click(pt) => { 180 | if bounds.contains(pt) { 181 | self.press_event.emit_owned(pt); 182 | } 183 | } 184 | GlobalEvent::MouseMove(pt) => { 185 | let before = std::mem::replace(&mut self.hover, bounds.contains(pt)); 186 | if self.hover != before { 187 | self.command_group.repaint(); 188 | } 189 | } 190 | } 191 | } 192 | } 193 | 194 | fn draw(&mut self, display: &mut dyn GraphicsDisplay, _aux: &mut ()) { 195 | if self.font.is_none() { 196 | self.font = display 197 | .new_resource(ResourceDescriptor::Font(ResourceData::Data(SharedData::RefCount( 198 | std::sync::Arc::new(self.font_info.data().unwrap()), 199 | )))) 200 | .ok(); 201 | } 202 | 203 | let bounds = self.bounds(); 204 | let color = if self.hover { 205 | Color::new(0.25, 0.60, 0.70, 1.0) 206 | } else { 207 | Color::new(0.20, 0.55, 0.65, 1.0) 208 | }; 209 | 210 | let mut builder = DisplayListBuilder::new(); 211 | 212 | builder.push_round_rectangle( 213 | bounds, 214 | [10.0; 4], 215 | GraphicsDisplayPaint::Fill(color.into()), 216 | None, 217 | ); 218 | 219 | builder.push_text( 220 | TextDisplayItem { 221 | text: self.text.clone().into(), 222 | font: self.font.as_ref().unwrap().clone(), 223 | font_info: self.font_info.clone(), 224 | size: 22.0, 225 | bottom_left: bounds.origin.add_size(&Size::new(10.0, bounds.size.height / 2.0)), 226 | color: Color::new(1.0, 1.0, 1.0, 1.0).into(), 227 | }, 228 | None, 229 | ); 230 | 231 | self.command_group.push(display, &builder.build(), Default::default(), None, None).unwrap(); 232 | } 233 | } 234 | 235 | fn main() { 236 | let window_size = (500u32, 500u32); 237 | 238 | let event_loop = EventLoop::new(); 239 | 240 | let wb = 241 | glutin::window::WindowBuilder::new().with_title("Counter with Reclutch").with_inner_size( 242 | glutin::dpi::PhysicalSize::new(window_size.0 as f64, window_size.1 as f64), 243 | ); 244 | 245 | let context = 246 | glutin::ContextBuilder::new().with_vsync(true).build_windowed(wb, &event_loop).unwrap(); 247 | 248 | let context = unsafe { context.make_current().unwrap() }; 249 | 250 | gl::load_with(|s| context.get_proc_address(s)); 251 | 252 | let mut fboid = 0; 253 | unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) }; 254 | 255 | let mut display = display::skia::SkiaGraphicsDisplay::new_gl_framebuffer( 256 | |s| context.get_proc_address(s), 257 | &display::skia::SkiaOpenGlFramebuffer { 258 | framebuffer_id: fboid as _, 259 | size: (window_size.0 as _, window_size.1 as _), 260 | }, 261 | ) 262 | .unwrap(); 263 | 264 | // set up the UI 265 | let mut window_q = RcEventQueue::default(); 266 | let mut counter = Counter::new(&mut window_q); 267 | let mut cursor = Point::default(); 268 | 269 | event_loop.run(move |event, _, control_flow| { 270 | *control_flow = ControlFlow::Wait; 271 | 272 | match event { 273 | WinitEvent::RedrawRequested { .. } => { 274 | counter.draw(&mut display, &mut ()); 275 | display.present(None).unwrap(); 276 | context.swap_buffers().unwrap(); 277 | } 278 | WinitEvent::WindowEvent { 279 | event: WindowEvent::CursorMoved { position, .. }, .. 280 | } => { 281 | cursor = Point::new(position.x as _, position.y as _); 282 | 283 | window_q.emit_owned(GlobalEvent::MouseMove(cursor)); 284 | } 285 | WinitEvent::WindowEvent { 286 | event: 287 | WindowEvent::MouseInput { 288 | state: glutin::event::ElementState::Pressed, 289 | button: glutin::event::MouseButton::Left, 290 | .. 291 | }, 292 | .. 293 | } => { 294 | window_q.emit_owned(GlobalEvent::Click(cursor)); 295 | } 296 | WinitEvent::WindowEvent { event: WindowEvent::CloseRequested, .. } => { 297 | *control_flow = ControlFlow::Exit; 298 | } 299 | WinitEvent::WindowEvent { event: WindowEvent::Resized(size), .. } => { 300 | display.resize((size.width as _, size.height as _)).unwrap(); 301 | context.resize(size); 302 | } 303 | _ => return, 304 | } 305 | 306 | counter.update(&mut ()); 307 | context.window().request_redraw(); 308 | }); 309 | } 310 | -------------------------------------------------------------------------------- /reclutch/examples/image_viewer/ferris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzfool/reclutch/2fbd297fbc007f293ea6e5f5baff4ee0c95812d3/reclutch/examples/image_viewer/ferris.png -------------------------------------------------------------------------------- /reclutch/examples/image_viewer/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzfool/reclutch/2fbd297fbc007f293ea6e5f5baff4ee0c95812d3/reclutch/examples/image_viewer/image.jpg -------------------------------------------------------------------------------- /reclutch/examples/image_viewer/main.rs: -------------------------------------------------------------------------------- 1 | use { 2 | glium::glutin::{ 3 | self, 4 | event::{Event, WindowEvent}, 5 | event_loop::{ControlFlow, EventLoop}, 6 | }, 7 | reclutch::{ 8 | display::{ 9 | self, Color, CommandGroup, DisplayCommand, DisplayListBuilder, Filter, FontInfo, 10 | GraphicsDisplay, GraphicsDisplayPaint, GraphicsDisplayStroke, ImageData, Point, Rect, 11 | ResourceData, ResourceDescriptor, ResourceReference, SharedData, Size, TextDisplayItem, 12 | Vector, 13 | }, 14 | event::{merge::Merge, RcEventListener, RcEventQueue}, 15 | gl, 16 | prelude::*, 17 | WidgetChildren, 18 | }, 19 | }; 20 | 21 | #[derive(Clone)] 22 | struct ConsumableEvent(std::rc::Rc>>); 23 | 24 | impl ConsumableEvent { 25 | fn new(val: T) -> Self { 26 | ConsumableEvent(std::rc::Rc::new(std::cell::RefCell::new(Some(val)))) 27 | } 28 | 29 | fn with bool>(&self, mut pred: P) -> Option { 30 | if self.0.borrow().is_some() { 31 | if pred(self.0.borrow().as_ref().unwrap()) { 32 | return self.0.replace(None); 33 | } 34 | } 35 | 36 | None 37 | } 38 | } 39 | 40 | #[derive(Clone)] 41 | enum GlobalEvent { 42 | MouseClick(ConsumableEvent), 43 | MouseRelease(Point), 44 | MouseMove(Point), 45 | WindowResize, 46 | } 47 | 48 | struct Globals { 49 | cursor: Point, 50 | size: Size, 51 | } 52 | 53 | #[derive(Debug, Clone, Copy)] 54 | enum TitlebarEvent { 55 | BeginClick(Point), 56 | Move(Vector), 57 | EndClick, 58 | } 59 | 60 | #[derive(WidgetChildren)] 61 | struct Titlebar { 62 | pub move_event: RcEventQueue, 63 | position: Point, 64 | cursor_anchor: Option, 65 | global_listener: RcEventListener, 66 | command_group: CommandGroup, 67 | width: f32, 68 | text: String, 69 | font: FontInfo, 70 | font_resource: Option, 71 | } 72 | 73 | impl Titlebar { 74 | fn new( 75 | position: Point, 76 | width: f32, 77 | text: String, 78 | global: &mut RcEventQueue, 79 | ) -> Self { 80 | Titlebar { 81 | move_event: RcEventQueue::default(), 82 | position, 83 | cursor_anchor: None, 84 | global_listener: global.listen(), 85 | command_group: CommandGroup::new(), 86 | width, 87 | text, 88 | font: FontInfo::from_name("Segoe UI", &["SF Display", "Arial"], None).unwrap(), 89 | font_resource: None, 90 | } 91 | } 92 | 93 | fn set_position(&mut self, position: Point) { 94 | self.position = position; 95 | self.command_group.repaint(); 96 | } 97 | } 98 | 99 | impl Widget for Titlebar { 100 | type UpdateAux = Globals; 101 | type GraphicalAux = (); 102 | type DisplayObject = DisplayCommand; 103 | 104 | fn bounds(&self) -> Rect { 105 | Rect::new(self.position, Size::new(self.width, 30.0)) 106 | } 107 | 108 | fn update(&mut self, _aux: &mut Globals) { 109 | for event in self.global_listener.peek() { 110 | match event { 111 | GlobalEvent::MouseClick(click) => { 112 | if let Some(ref position) = 113 | click.with(|pos| self.bounds().contains(pos.clone())) 114 | { 115 | self.cursor_anchor = Some(position.clone()); 116 | self.move_event.emit_owned(TitlebarEvent::BeginClick(position.clone())); 117 | } 118 | } 119 | GlobalEvent::MouseRelease(_) => { 120 | if self.cursor_anchor.is_some() { 121 | self.cursor_anchor = None; 122 | self.move_event.emit_owned(TitlebarEvent::EndClick); 123 | } 124 | } 125 | GlobalEvent::MouseMove(pos) => { 126 | if let Some(ref cursor_anchor) = self.cursor_anchor { 127 | self.move_event.emit_owned(TitlebarEvent::Move(pos - *cursor_anchor)); 128 | } 129 | } 130 | _ => (), 131 | } 132 | } 133 | } 134 | 135 | fn draw(&mut self, display: &mut dyn GraphicsDisplay, _aux: &mut ()) { 136 | if self.font_resource.is_none() { 137 | self.font_resource = display 138 | .new_resource(ResourceDescriptor::Font(ResourceData::Data(SharedData::RefCount( 139 | std::sync::Arc::new(self.font.data().unwrap()), 140 | )))) 141 | .ok(); 142 | } 143 | 144 | let bounds = self.bounds(); 145 | 146 | let mut builder = DisplayListBuilder::new(); 147 | 148 | builder.push_rectangle_backdrop(bounds, true, Filter::Blur(10.0, 10.0)); 149 | 150 | builder.push_rectangle( 151 | bounds, 152 | GraphicsDisplayPaint::Fill(Color::new(1.0, 1.0, 1.0, 0.6).into()), 153 | None, 154 | ); 155 | 156 | builder.push_line( 157 | Point::new(bounds.origin.x, bounds.origin.y + bounds.size.height), 158 | Point::new(bounds.origin.x + bounds.size.width, bounds.origin.y + bounds.size.height), 159 | GraphicsDisplayStroke { thickness: 1.0, antialias: false, ..Default::default() }, 160 | None, 161 | ); 162 | 163 | builder.push_text( 164 | TextDisplayItem { 165 | text: self.text.clone().into(), 166 | font: self.font_resource.as_ref().unwrap().clone(), 167 | font_info: self.font.clone(), 168 | size: 22.0, 169 | bottom_left: bounds.origin + Size::new(5.0, 22.0), 170 | color: Color::new(0.0, 0.0, 0.0, 1.0).into(), 171 | }, 172 | None, 173 | ); 174 | 175 | self.command_group.push(display, &builder.build(), Default::default(), None, None).unwrap(); 176 | } 177 | } 178 | 179 | #[derive(WidgetChildren)] 180 | struct Panel { 181 | pub on_click: RcEventQueue<*const Panel>, 182 | #[widget_child] 183 | titlebar: Titlebar, 184 | position_anchor: Option, 185 | position: Point, 186 | size: Size, 187 | global_listener: RcEventListener, 188 | titlebar_move_listener: RcEventListener, 189 | command_group: CommandGroup, 190 | image_data: &'static [u8], 191 | image: Option, 192 | } 193 | 194 | impl Panel { 195 | fn new( 196 | position: Point, 197 | size: Size, 198 | text: String, 199 | image_data: &'static [u8], 200 | global: &mut RcEventQueue, 201 | ) -> Self { 202 | let titlebar = Titlebar::new(position.clone(), size.width - 1.0, text, global); 203 | let titlebar_move_listener = titlebar.move_event.listen(); 204 | 205 | Panel { 206 | on_click: RcEventQueue::default(), 207 | titlebar, 208 | position_anchor: None, 209 | position, 210 | size, 211 | global_listener: global.listen(), 212 | titlebar_move_listener, 213 | command_group: CommandGroup::new(), 214 | image_data, 215 | image: None, 216 | } 217 | } 218 | 219 | fn fit_in_window(&mut self, size: &Size) { 220 | let window_rect = Rect::new(Point::default(), size.clone()); 221 | let bounds = self.bounds(); 222 | 223 | let vert = if bounds.min_y() < window_rect.min_y() { 224 | window_rect.min_y() - bounds.min_y() 225 | } else if bounds.max_y() > window_rect.max_y() { 226 | window_rect.max_y() - bounds.max_y() 227 | } else { 228 | 0.0 229 | }; 230 | 231 | let horiz = if bounds.min_x() < window_rect.min_x() { 232 | window_rect.min_x() - bounds.min_x() 233 | } else if bounds.max_x() > window_rect.max_x() { 234 | window_rect.max_x() - bounds.max_x() 235 | } else { 236 | 0.0 237 | }; 238 | 239 | self.position += Vector::new(horiz, vert); 240 | } 241 | } 242 | 243 | impl Widget for Panel { 244 | type UpdateAux = Globals; 245 | type GraphicalAux = (); 246 | type DisplayObject = DisplayCommand; 247 | 248 | fn bounds(&self) -> Rect { 249 | Rect::new(self.position, self.size) 250 | } 251 | 252 | fn update(&mut self, aux: &mut Globals) { 253 | for child in self.children_mut() { 254 | child.update(aux); 255 | } 256 | 257 | for event in self.titlebar_move_listener.peek() { 258 | match event { 259 | TitlebarEvent::BeginClick(_) => { 260 | self.position_anchor = Some(self.position); 261 | self.on_click.emit_owned(self as _); 262 | } 263 | TitlebarEvent::Move(delta) => { 264 | if let Some(position_anchor) = self.position_anchor { 265 | self.position = position_anchor + delta; 266 | 267 | self.fit_in_window(&aux.size); 268 | 269 | self.titlebar.set_position(self.position.clone()); 270 | self.command_group.repaint(); 271 | } 272 | } 273 | TitlebarEvent::EndClick => { 274 | self.position_anchor = None; 275 | } 276 | } 277 | } 278 | 279 | for event in self.global_listener.peek() { 280 | match event { 281 | GlobalEvent::MouseClick(click) => { 282 | if let Some(_) = click.with(|pos| self.bounds().contains(pos.clone())) { 283 | self.on_click.emit_owned(self as _); 284 | self.command_group.repaint(); 285 | self.titlebar.command_group.repaint(); 286 | } 287 | } 288 | GlobalEvent::WindowResize => { 289 | self.fit_in_window(&aux.size); 290 | 291 | self.titlebar.set_position(self.position.clone()); 292 | self.command_group.repaint(); 293 | } 294 | _ => (), 295 | } 296 | } 297 | } 298 | 299 | fn draw(&mut self, display: &mut dyn GraphicsDisplay, aux: &mut ()) { 300 | if self.image.is_none() { 301 | self.image = display 302 | .new_resource(ResourceDescriptor::Image(ImageData::Encoded(ResourceData::Data( 303 | SharedData::Static(self.image_data), 304 | )))) 305 | .ok(); 306 | } 307 | 308 | let bounds = self.bounds(); 309 | 310 | let mut builder = DisplayListBuilder::new(); 311 | 312 | builder.push_rectangle_backdrop(bounds, true, Filter::Blur(5.0, 5.0)); 313 | 314 | builder.push_rectangle( 315 | bounds, 316 | GraphicsDisplayPaint::Fill(Color::new(0.9, 0.9, 0.9, 0.5).into()), 317 | None, 318 | ); 319 | 320 | builder.push_image(None, bounds, self.image.clone().unwrap(), None); 321 | 322 | builder.push_rectangle( 323 | bounds.inflate(0.0, 0.5), 324 | GraphicsDisplayPaint::Stroke(GraphicsDisplayStroke { 325 | color: Color::new(0.0, 0.0, 0.0, 1.0).into(), 326 | thickness: 1.0, 327 | antialias: false, 328 | ..Default::default() 329 | }), 330 | None, 331 | ); 332 | 333 | self.command_group.push(display, &builder.build(), Default::default(), None, None).unwrap(); 334 | 335 | for child in self.children_mut() { 336 | child.draw(display, aux); 337 | } 338 | } 339 | } 340 | 341 | #[derive(WidgetChildren)] 342 | struct PanelContainer { 343 | #[vec_widget_child] 344 | panels: Vec, 345 | listeners: Vec>, 346 | } 347 | 348 | impl PanelContainer { 349 | fn new() -> Self { 350 | PanelContainer { panels: Vec::new(), listeners: Vec::new() } 351 | } 352 | 353 | fn add_panel(&mut self, panel: Panel) { 354 | let on_click_listener = panel.on_click.listen(); 355 | self.panels.push(panel); 356 | self.listeners.push(on_click_listener); 357 | } 358 | } 359 | 360 | impl Widget for PanelContainer { 361 | type UpdateAux = Globals; 362 | type GraphicalAux = (); 363 | type DisplayObject = DisplayCommand; 364 | 365 | fn update(&mut self, globals: &mut Globals) { 366 | // propagate back to front so that panels rendered front-most get events first. 367 | for child in self.children_mut().iter_mut().rev() { 368 | child.update(globals); 369 | } 370 | 371 | { 372 | // collect all the panel events into a single vec 373 | let mut panel_events = Vec::new(); 374 | for listener in &self.listeners { 375 | listener.extend_other(&mut panel_events); 376 | } 377 | 378 | for event in panel_events { 379 | if let Some(panel_idx) = self.panels.iter().position(|p| p as *const Panel == event) 380 | { 381 | let last = self.panels.len() - 1; 382 | self.panels.swap(panel_idx, last); 383 | } 384 | } 385 | } 386 | } 387 | 388 | fn draw(&mut self, display: &mut dyn GraphicsDisplay, aux: &mut ()) { 389 | for child in self.children_mut() { 390 | child.draw(display, aux); 391 | } 392 | } 393 | } 394 | 395 | fn main() { 396 | let window_size = (500u32, 500u32); 397 | 398 | let event_loop = EventLoop::new(); 399 | 400 | let wb = glutin::window::WindowBuilder::new() 401 | .with_title("Image Viewer with Reclutch") 402 | .with_inner_size(glutin::dpi::PhysicalSize::new(window_size.0 as f64, window_size.1 as f64)) 403 | .with_min_inner_size(glutin::dpi::PhysicalSize::new(400.0, 200.0)); 404 | 405 | let context = glutin::ContextBuilder::new() 406 | .with_vsync(true) // fast dragging motion at the cost of high GPU usage 407 | .build_windowed(wb, &event_loop) 408 | .unwrap(); 409 | 410 | let context = unsafe { context.make_current().unwrap() }; 411 | 412 | gl::load_with(|s| context.get_proc_address(s)); 413 | 414 | let mut fboid = 0; 415 | unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) }; 416 | 417 | let mut display = display::skia::SkiaGraphicsDisplay::new_gl_framebuffer( 418 | |s| context.get_proc_address(s), 419 | &display::skia::SkiaOpenGlFramebuffer { 420 | framebuffer_id: fboid as _, 421 | size: (window_size.0 as _, window_size.1 as _), 422 | }, 423 | ) 424 | .unwrap(); 425 | 426 | display 427 | .push_command_group( 428 | &[DisplayCommand::Clear(Color::new(1.0, 1.0, 1.0, 1.0))], 429 | Default::default(), 430 | None, 431 | Some(false), 432 | ) 433 | .unwrap(); 434 | 435 | let mut global_q = RcEventQueue::default(); 436 | 437 | let mut globals = Globals { 438 | cursor: Point::default(), 439 | size: Size::new(window_size.0 as _, window_size.1 as _), 440 | }; 441 | 442 | let mut panel_container = PanelContainer::new(); 443 | 444 | panel_container.add_panel(Panel::new( 445 | Point::new(10.0, 10.0), 446 | Size::new(288.0, 180.15), 447 | "Ferris".into(), 448 | include_bytes!("ferris.png"), 449 | &mut global_q, 450 | )); 451 | 452 | panel_container.add_panel(Panel::new( 453 | Point::new(30.0, 30.0), 454 | Size::new(300.0, 200.0), 455 | "Forest".into(), 456 | include_bytes!("image.jpg"), 457 | &mut global_q, 458 | )); 459 | 460 | event_loop.run(move |event, _, control_flow| { 461 | *control_flow = ControlFlow::Wait; 462 | 463 | match event { 464 | Event::RedrawRequested { .. } => { 465 | panel_container.draw(&mut display, &mut ()); 466 | display.present(None).unwrap(); 467 | context.swap_buffers().unwrap(); 468 | } 469 | Event::WindowEvent { event: WindowEvent::CursorMoved { position, .. }, .. } => { 470 | globals.cursor = Point::new(position.x as _, position.y as _); 471 | global_q.emit_owned(GlobalEvent::MouseMove(globals.cursor.clone())); 472 | } 473 | Event::WindowEvent { 474 | event: 475 | WindowEvent::MouseInput { button: glutin::event::MouseButton::Left, state, .. }, 476 | .. 477 | } => match state { 478 | glutin::event::ElementState::Pressed => { 479 | global_q.emit_owned(GlobalEvent::MouseClick(ConsumableEvent::new( 480 | globals.cursor.clone(), 481 | ))); 482 | } 483 | glutin::event::ElementState::Released => { 484 | global_q.emit_owned(GlobalEvent::MouseRelease(globals.cursor.clone())); 485 | } 486 | }, 487 | Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { 488 | *control_flow = ControlFlow::Exit; 489 | } 490 | Event::WindowEvent { event: WindowEvent::Resized(size), .. } => { 491 | display.resize((size.width as _, size.height as _)).unwrap(); 492 | context.resize(size); 493 | 494 | globals.size.width = size.width as _; 495 | globals.size.height = size.height as _; 496 | global_q.emit_owned(GlobalEvent::WindowResize); 497 | } 498 | _ => return, 499 | } 500 | 501 | panel_container.update(&mut globals); 502 | context.window().request_redraw(); 503 | }); 504 | } 505 | -------------------------------------------------------------------------------- /reclutch/examples/opengl/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate glium; 3 | 4 | use { 5 | glium::{ 6 | glutin::{ 7 | self, 8 | event::{Event, WindowEvent}, 9 | event_loop::{ControlFlow, EventLoop}, 10 | }, 11 | GlObject, Surface, 12 | }, 13 | reclutch::display::{ 14 | self, Color, DisplayListBuilder, Filter, GraphicsDisplay, GraphicsDisplayPaint, Point, 15 | Rect, Size, 16 | }, 17 | }; 18 | 19 | #[derive(Copy, Clone)] 20 | struct Vertex { 21 | position: [f32; 3], 22 | normal: [f32; 3], 23 | } 24 | 25 | implement_vertex!(Vertex, position, normal); 26 | 27 | const fn vertex(pos: [i8; 3], nor: [i8; 3]) -> Vertex { 28 | Vertex { 29 | position: [pos[0] as _, pos[1] as _, pos[2] as _], 30 | normal: [nor[0] as _, nor[1] as _, nor[2] as _], 31 | } 32 | } 33 | 34 | #[derive(Copy, Clone)] 35 | struct TextureVertex { 36 | position: [f32; 3], 37 | tex_coord: [f32; 2], 38 | } 39 | 40 | implement_vertex!(TextureVertex, position, tex_coord); 41 | 42 | const fn texture_vertex(pos: [i8; 2], tex: [i8; 2]) -> TextureVertex { 43 | TextureVertex { 44 | position: [pos[0] as _, pos[1] as _, 0.0], 45 | tex_coord: [tex[0] as _, tex[1] as _], 46 | } 47 | } 48 | 49 | const CUBE_VERTICES: [Vertex; 24] = [ 50 | vertex([-1, -1, 1], [0, 0, 1]), 51 | vertex([1, -1, 1], [0, 0, 1]), 52 | vertex([1, 1, 1], [0, 0, 1]), 53 | vertex([-1, 1, 1], [0, 0, 1]), 54 | vertex([-1, 1, -1], [0, 0, -1]), 55 | vertex([1, 1, -1], [0, 0, -1]), 56 | vertex([1, -1, -1], [0, 0, -1]), 57 | vertex([-1, -1, -1], [0, 0, -1]), 58 | vertex([1, -1, -1], [1, 0, 0]), 59 | vertex([1, 1, -1], [1, 0, 0]), 60 | vertex([1, 1, 1], [1, 0, 0]), 61 | vertex([1, -1, 1], [1, 0, 0]), 62 | vertex([-1, -1, 1], [-1, 0, 0]), 63 | vertex([-1, 1, 1], [-1, 0, 0]), 64 | vertex([-1, 1, -1], [-1, 0, 0]), 65 | vertex([-1, -1, -1], [-1, 0, 0]), 66 | vertex([1, 1, -1], [0, 1, 0]), 67 | vertex([-1, 1, -1], [0, 1, 0]), 68 | vertex([-1, 1, 1], [0, 1, 0]), 69 | vertex([1, 1, 1], [0, 1, 0]), 70 | vertex([1, -1, 1], [0, -1, 0]), 71 | vertex([-1, -1, 1], [0, -1, 0]), 72 | vertex([-1, -1, -1], [0, -1, 0]), 73 | vertex([1, -1, -1], [0, -1, 0]), 74 | ]; 75 | 76 | const CUBE_INDICES: [u32; 36] = [ 77 | 0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4, 8, 9, 10, 10, 11, 8, 12, 13, 14, 14, 15, 12, 16, 17, 18, 78 | 18, 19, 16, 20, 21, 22, 22, 23, 20, 79 | ]; 80 | 81 | const QUAD_VERTICES: [TextureVertex; 4] = [ 82 | texture_vertex([-1, -1], [0, 0]), 83 | texture_vertex([-1, 1], [0, 1]), 84 | texture_vertex([1, 1], [1, 1]), 85 | texture_vertex([1, -1], [1, 0]), 86 | ]; 87 | 88 | const QUAD_INDICES: [u32; 6] = [0, 1, 2, 0, 2, 3]; 89 | 90 | fn main() { 91 | let window_size = (500u32, 500u32); 92 | 93 | let event_loop = EventLoop::new(); 94 | 95 | let wb = glutin::window::WindowBuilder::new() 96 | .with_title("OpenGL 3D with Reclutch") 97 | .with_inner_size(glutin::dpi::PhysicalSize::new(window_size.0 as f64, window_size.1 as f64)) 98 | .with_resizable(false); 99 | 100 | let cb = glutin::ContextBuilder::new().with_vsync(true).with_srgb(true); 101 | 102 | let gl_display = glium::Display::new(wb, cb, &event_loop).unwrap(); 103 | 104 | let vertex_buffer = glium::VertexBuffer::new(&gl_display, &CUBE_VERTICES).unwrap(); 105 | let indices = glium::IndexBuffer::new( 106 | &gl_display, 107 | glium::index::PrimitiveType::TrianglesList, 108 | &CUBE_INDICES, 109 | ) 110 | .unwrap(); 111 | 112 | let quad_vertex_buffer = glium::VertexBuffer::new(&gl_display, &QUAD_VERTICES).unwrap(); 113 | let quad_indices = glium::IndexBuffer::new( 114 | &gl_display, 115 | glium::index::PrimitiveType::TrianglesList, 116 | &QUAD_INDICES, 117 | ) 118 | .unwrap(); 119 | 120 | let vertex_shader_src = r#" 121 | #version 150 122 | 123 | in vec3 position; 124 | in vec3 normal; 125 | 126 | out vec3 v_normal; 127 | 128 | uniform mat4 matrix; 129 | 130 | void main() { 131 | v_normal = transpose(inverse(mat3(matrix))) * normal; 132 | gl_Position = matrix * vec4(position, 1.0); 133 | } 134 | "#; 135 | 136 | let fragment_shader_src = r#" 137 | #version 150 138 | 139 | in vec3 v_normal; 140 | out vec4 frag_color; 141 | 142 | uniform vec3 light; 143 | 144 | void main() { 145 | float brightness = dot(normalize(v_normal), normalize(light)); 146 | vec3 dark = vec3(0.32, 0.5, 0.5); 147 | vec3 regular = vec3(0.55, 0.9, 0.9); 148 | frag_color = vec4(mix(dark, regular, brightness), 1.0); 149 | } 150 | "#; 151 | 152 | let quad_vertex_shader_src = r#" 153 | #version 140 154 | 155 | in vec3 position; 156 | in vec2 tex_coord; 157 | 158 | out vec2 frag_tex_coord; 159 | 160 | void main() { 161 | frag_tex_coord = tex_coord; 162 | gl_Position = vec4(position, 1.0); 163 | } 164 | "#; 165 | 166 | let quad_fragment_shader_src = r#" 167 | #version 150 168 | 169 | in vec2 frag_tex_coord; 170 | out vec4 color; 171 | 172 | uniform sampler2D tex; 173 | 174 | void main() { 175 | color = texture(tex, frag_tex_coord); 176 | } 177 | "#; 178 | 179 | let program = 180 | glium::Program::from_source(&gl_display, vertex_shader_src, fragment_shader_src, None) 181 | .unwrap(); 182 | 183 | let quad_program = glium::Program::from_source( 184 | &gl_display, 185 | quad_vertex_shader_src, 186 | quad_fragment_shader_src, 187 | None, 188 | ) 189 | .unwrap(); 190 | 191 | let out_texture = glium::texture::SrgbTexture2d::empty_with_format( 192 | &gl_display, 193 | glium::texture::SrgbFormat::U8U8U8U8, 194 | glium::texture::MipmapsOption::NoMipmap, 195 | window_size.0, 196 | window_size.1, 197 | ) 198 | .unwrap(); 199 | let out_texture_depth = 200 | glium::texture::DepthTexture2d::empty(&gl_display, window_size.0, window_size.1).unwrap(); 201 | 202 | let skia_context = unsafe { 203 | glutin::ContextBuilder::new() 204 | .with_gl(glutin::GlRequest::Specific(glutin::Api::OpenGl, (3, 3))) 205 | .with_shared_lists(&gl_display.gl_window()) 206 | .with_srgb(true) 207 | .build_headless( 208 | &event_loop, 209 | glutin::dpi::PhysicalSize::new(window_size.0 as _, window_size.1 as _), 210 | ) 211 | .unwrap() 212 | .make_current() 213 | .unwrap() 214 | }; 215 | 216 | let mut display = display::skia::SkiaGraphicsDisplay::new_gl_texture( 217 | |s| skia_context.get_proc_address(s), 218 | &display::skia::SkiaOpenGlTexture { 219 | size: (window_size.0 as _, window_size.1 as _), 220 | texture_id: out_texture.get_id(), 221 | mip_mapped: false, 222 | }, 223 | ) 224 | .unwrap(); 225 | 226 | let mut skia_context = Some(unsafe { skia_context.make_not_current().unwrap() }); 227 | 228 | { 229 | let rect = Rect::new(Point::new(150.0, 150.0), Size::new(100.0, 150.0)); 230 | 231 | let mut builder = DisplayListBuilder::new(); 232 | 233 | builder.push_round_rectangle_backdrop(rect, [20.0; 4], Filter::Blur(10.0, 10.0)); 234 | 235 | builder.push_round_rectangle( 236 | rect, 237 | [20.0; 4], 238 | GraphicsDisplayPaint::Fill(Color::new(0.0, 0.0, 0.0, 0.2).into()), 239 | None, 240 | ); 241 | 242 | display.push_command_group(&builder.build(), Default::default(), None, Some(false)).unwrap() 243 | }; 244 | 245 | let mut roll = 0.0; 246 | let mut pitch = 0.0; 247 | let mut yaw = 0.0; 248 | 249 | event_loop.run(move |event, _, control_flow| { 250 | *control_flow = ControlFlow::WaitUntil( 251 | std::time::Instant::now() + std::time::Duration::from_nanos(16_666_667), 252 | ); 253 | 254 | match event { 255 | Event::RedrawRequested { .. } => { 256 | roll += 0.001; 257 | pitch += 0.002; 258 | yaw += 0.003; 259 | 260 | let mut out_texture_fb = glium::framebuffer::SimpleFrameBuffer::with_depth_buffer( 261 | &gl_display, 262 | &out_texture, 263 | &out_texture_depth, 264 | ) 265 | .unwrap(); 266 | 267 | let mut frame_target = gl_display.draw(); 268 | let target = &mut out_texture_fb; 269 | 270 | let na_matrix = 271 | nalgebra::Matrix4::from_euler_angles(roll, pitch, yaw).append_scaling(0.25); 272 | let matrix: &[[f32; 4]; 4] = na_matrix.as_ref(); 273 | 274 | let params = glium::DrawParameters { 275 | depth: glium::Depth { 276 | test: glium::draw_parameters::DepthTest::IfLess, 277 | write: true, 278 | ..Default::default() 279 | }, 280 | ..Default::default() 281 | }; 282 | 283 | target.clear_color_and_depth((1.0, 1.0, 1.0, 1.0), 1.0); 284 | target 285 | .draw( 286 | &vertex_buffer, 287 | &indices, 288 | &program, 289 | &uniform! { matrix: *matrix, light: [-1.0, 0.4, 0.9f32] }, 290 | ¶ms, 291 | ) 292 | .unwrap(); 293 | 294 | let skia_context_c = 295 | unsafe { skia_context.take().unwrap().make_current().unwrap() }; 296 | display.present(None).unwrap(); 297 | skia_context = Some(unsafe { skia_context_c.make_not_current().unwrap() }); 298 | 299 | frame_target 300 | .draw( 301 | &quad_vertex_buffer, 302 | &quad_indices, 303 | &quad_program, 304 | &uniform! { tex: &out_texture }, 305 | &Default::default(), 306 | ) 307 | .unwrap(); 308 | frame_target.finish().unwrap(); 309 | } 310 | Event::MainEventsCleared => { 311 | gl_display.gl_window().window().request_redraw(); 312 | } 313 | Event::WindowEvent { event: WindowEvent::CloseRequested, .. } => { 314 | *control_flow = ControlFlow::Exit; 315 | } 316 | _ => return, 317 | } 318 | }); 319 | } 320 | -------------------------------------------------------------------------------- /reclutch/examples/shaping/NotoSans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jazzfool/reclutch/2fbd297fbc007f293ea6e5f5baff4ee0c95812d3/reclutch/examples/shaping/NotoSans.ttf -------------------------------------------------------------------------------- /reclutch/examples/shaping/main.rs: -------------------------------------------------------------------------------- 1 | use { 2 | glium::glutin::{ 3 | self, 4 | event::{Event as WinitEvent, WindowEvent}, 5 | event_loop::{ControlFlow, EventLoop}, 6 | }, 7 | reclutch::{ 8 | display::{ 9 | self, Color, DisplayListBuilder, DisplayText, FontInfo, GraphicsDisplay as _, Point, 10 | ResourceData, ResourceDescriptor, ShapedGlyph, SharedData, TextDisplayItem, Vector, 11 | }, 12 | gl, 13 | }, 14 | }; 15 | 16 | const FONT_SIZE: i32 = 64; 17 | 18 | fn shape_with_harfbuzz(text: &str, size: i32) -> Vec { 19 | use harfbuzz_rs as hb; 20 | 21 | let face = hb::Face::from_bytes(include_bytes!("NotoSans.ttf"), 0); 22 | let mut font = hb::Font::new(face); 23 | 24 | font.set_scale(size, size); 25 | 26 | let buffer = hb::UnicodeBuffer::new().add_str(text); 27 | let output = hb::shape(&font, buffer, &[]); 28 | 29 | output 30 | .get_glyph_positions() 31 | .iter() 32 | .zip(output.get_glyph_infos()) 33 | .map(|(position, info)| ShapedGlyph { 34 | codepoint: info.codepoint, 35 | offset: Vector::new(position.x_offset as _, position.y_offset as _), 36 | advance: Vector::new(position.x_advance as _, position.y_advance as _), 37 | }) 38 | .collect() 39 | } 40 | 41 | fn shape_with_rusttype(text: &str, size: i32) -> Vec { 42 | use rusttype::Font; 43 | 44 | let font = Font::try_from_bytes(include_bytes!("NotoSans.ttf")).unwrap(); 45 | 46 | font.layout( 47 | text, 48 | rusttype::Scale::uniform(size as f32 * 1.35), 49 | rusttype::Point { x: 0.0, y: 0.0 }, 50 | ) 51 | .map(|glyph| ShapedGlyph { 52 | codepoint: glyph.id().0 as _, 53 | offset: Vector::new(0.0, glyph.position().y), 54 | advance: Vector::new(glyph.unpositioned().h_metrics().advance_width, 0.0), 55 | }) 56 | .collect() 57 | } 58 | 59 | fn main() { 60 | let window_size = (500u32, 500u32); 61 | 62 | let event_loop = EventLoop::new(); 63 | 64 | let wb = glutin::window::WindowBuilder::new() 65 | .with_title("Text shaping with Reclutch") 66 | .with_inner_size(glutin::dpi::PhysicalSize::new( 67 | window_size.0 as f64, 68 | window_size.1 as f64, 69 | )); 70 | 71 | let context = 72 | glutin::ContextBuilder::new().with_vsync(true).build_windowed(wb, &event_loop).unwrap(); 73 | 74 | let context = unsafe { context.make_current().unwrap() }; 75 | 76 | gl::load_with(|s| context.get_proc_address(s)); 77 | 78 | let mut fboid = 0; 79 | unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) }; 80 | 81 | let mut display = display::skia::SkiaGraphicsDisplay::new_gl_framebuffer( 82 | |s| context.get_proc_address(s), 83 | &display::skia::SkiaOpenGlFramebuffer { 84 | framebuffer_id: fboid as _, 85 | size: (window_size.0 as _, window_size.1 as _), 86 | }, 87 | ) 88 | .unwrap(); 89 | 90 | { 91 | let font_data = std::sync::Arc::new(include_bytes!("NotoSans.ttf").to_vec()); 92 | let font_resource = display 93 | .new_resource(ResourceDescriptor::Font(ResourceData::Data(SharedData::RefCount( 94 | font_data.clone(), 95 | )))) 96 | .unwrap(); 97 | let font_info = FontInfo::from_data(font_data, 0).unwrap(); 98 | 99 | let text_blobs = vec![ 100 | TextDisplayItem { 101 | font: font_resource.clone(), 102 | font_info: font_info.clone(), 103 | size: 32.0, 104 | text: String::from("HarfBuzz").into(), 105 | color: Color::new(0.0, 0.0, 0.0, 1.0).into(), 106 | bottom_left: Point::new(40.0, 42.0), 107 | }, 108 | TextDisplayItem { 109 | font: font_resource.clone(), 110 | font_info: font_info.clone(), 111 | size: FONT_SIZE as _, 112 | text: DisplayText::Shaped(shape_with_harfbuzz("एकोऽयम्", FONT_SIZE)), 113 | color: Color::new(0.0, 0.0, 0.0, 1.0).into(), 114 | bottom_left: Point::new(40.0, FONT_SIZE as f32 + 60.0), 115 | }, 116 | TextDisplayItem { 117 | font: font_resource.clone(), 118 | font_info: font_info.clone(), 119 | size: 32.0, 120 | text: String::from("RustType").into(), 121 | color: Color::new(0.0, 0.0, 0.0, 1.0).into(), 122 | bottom_left: Point::new(40.0, 190.0), 123 | }, 124 | TextDisplayItem { 125 | font: font_resource.clone(), 126 | font_info: font_info.clone(), 127 | size: FONT_SIZE as f32, 128 | text: DisplayText::Shaped(shape_with_rusttype("एकोऽयम्", FONT_SIZE)), 129 | color: Color::new(0.0, 0.0, 0.0, 1.0).into(), 130 | bottom_left: Point::new(40.0, FONT_SIZE as f32 + 210.0), 131 | }, 132 | ]; 133 | 134 | let mut builder = DisplayListBuilder::new(); 135 | 136 | builder.push_clear(Color::new(1.0, 1.0, 1.0, 1.0)); 137 | 138 | for text_blob in text_blobs.into_iter() { 139 | let bbox = text_blob.bounds().unwrap(); 140 | 141 | builder.push_round_rectangle( 142 | bbox, 143 | [5.0; 4], 144 | display::GraphicsDisplayPaint::Fill(Color::new(0.0, 0.4, 1.0, 0.25).into()), 145 | None, 146 | ); 147 | builder.push_text(text_blob, None); 148 | } 149 | 150 | display 151 | .push_command_group(&builder.build(), Default::default(), None, Some(false)) 152 | .unwrap(); 153 | } 154 | 155 | event_loop.run(move |event, _, control_flow| { 156 | *control_flow = ControlFlow::Wait; 157 | 158 | match event { 159 | WinitEvent::RedrawRequested { .. } => { 160 | display.present(None).unwrap(); 161 | context.swap_buffers().unwrap(); 162 | } 163 | WinitEvent::WindowEvent { event: WindowEvent::CloseRequested, .. } => { 164 | *control_flow = ControlFlow::Exit; 165 | } 166 | WinitEvent::WindowEvent { event: WindowEvent::Resized(size), .. } => { 167 | display.resize((size.width as _, size.height as _)).unwrap(); 168 | context.resize(size); 169 | } 170 | _ => return, 171 | } 172 | 173 | context.window().request_redraw(); 174 | }); 175 | } 176 | -------------------------------------------------------------------------------- /reclutch/src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! Reclutch UI Core 2 | 3 | Reclutch is a barebones foundation to build a UI from, with a strong focus 4 | on control. 5 | 6 | # `Widget` 7 | 8 | A widget only defines 3 methods; [`bounds`], [`update`], and [`draw`]. 9 | It also defines 3 associated types ([`UpdateAux`], [`GraphicalAux`] and [`DisplayObject`]), discussed in relevant documentation. 10 | 11 | When implementing these methods, child widgets must be considered. Therefore 12 | it is advisable to propagate them; 13 | ```ignore 14 | for child in self.children_mut() { 15 | child.update(aux); 16 | // or: 17 | child.draw(display); 18 | } 19 | ``` 20 | The above example involves the `WidgetChildren` trait. 21 | 22 | # `WidgetChildren` 23 | 24 | [`WidgetChildren`] is a supertrait which defines an interface to collate all the 25 | child widgets from fields into a single [`Vec`]. 26 | 27 | Most of the time you don't want to implement [`WidgetChildren`] manually, instead 28 | you can use the provided `derive` crate to reduce it to a couple extra lines; 29 | ```ignore 30 | #[derive(WidgetChildren)] 31 | struct CounterWidget { 32 | // --snip-- 33 | 34 | #[widget_child] 35 | count_label: LabelWidget, 36 | #[widget_child] 37 | count_up: ButtonWidget, 38 | #[widget_child] 39 | count_down: ButtonWidget, 40 | } 41 | ``` 42 | This will resolve down to the following code: 43 | ```ignore 44 | impl reclutch::widget::WidgetChildren for CounterWidget { 45 | fn children( 46 | &self 47 | ) -> Vec< 48 | &dyn reclutch::widget::WidgetChildren< 49 | UpdateAux = Self::UpdateAux, 50 | GraphicalAux = Self::GraphicalAux, 51 | DisplayObject = Self::DisplayObject, 52 | > 53 | > { 54 | vec![&self.count_label, &self.count_up, &self.count_down] 55 | } 56 | 57 | fn children_mut( 58 | &mut self 59 | ) -> Vec< 60 | &dyn reclutch::widget::WidgetChildren< 61 | UpdateAux = Self::UpdateAux, 62 | GraphicalAux = Self::GraphicalAux, 63 | DisplayObject = Self::DisplayObject, 64 | > 65 | > { 66 | vec![&mut self.count_label, &mut self.count_up, &mut self.count_down] 67 | } 68 | } 69 | ``` 70 | 71 | [`bounds`]: widget::Widget::bounds 72 | [`update`]: widget::Widget::update 73 | [`draw`]: widget::Widget::draw 74 | [`UpdateAux`]: widget::Widget::UpdateAux 75 | [`GraphicalAux`]: widget::Widget::GraphicalAux 76 | [`DisplayObject`]: widget::Widget::DisplayObject 77 | [`WidgetChildren`]: widget::WidgetChildren 78 | **/ 79 | 80 | #[cfg(feature = "reclutch_derive")] 81 | #[allow(unused_imports)] 82 | #[macro_use] 83 | extern crate reclutch_derive; 84 | 85 | #[cfg(feature = "reclutch_derive")] 86 | pub use reclutch_derive::{Event, OperatesVerbGraph, WidgetChildren}; 87 | 88 | pub use reclutch_verbgraph as verbgraph; 89 | 90 | pub use reclutch_core::*; 91 | 92 | #[cfg(test)] 93 | mod tests { 94 | #[cfg(feature = "reclutch_derive")] 95 | #[test] 96 | fn test_widget_derive() { 97 | use crate as reclutch; 98 | use reclutch::{ 99 | display::{Point, Rect}, 100 | prelude::*, 101 | }; 102 | 103 | #[derive(WidgetChildren)] 104 | struct ExampleChild(i8); 105 | 106 | impl Widget for ExampleChild { 107 | type UpdateAux = (); 108 | type GraphicalAux = (); 109 | type DisplayObject = (); 110 | 111 | fn bounds(&self) -> Rect { 112 | Rect::new(Point::new(self.0 as _, 0.0), Default::default()) 113 | } 114 | } 115 | 116 | #[derive(WidgetChildren)] 117 | struct Unnamed( 118 | #[widget_child] ExampleChild, 119 | #[widget_child] ExampleChild, 120 | #[vec_widget_child] Vec, 121 | ); 122 | 123 | impl Widget for Unnamed { 124 | type UpdateAux = (); 125 | type GraphicalAux = (); 126 | type DisplayObject = (); 127 | } 128 | 129 | #[derive(WidgetChildren)] 130 | struct Named { 131 | #[widget_child] 132 | a: ExampleChild, 133 | #[widget_child] 134 | b: ExampleChild, 135 | #[vec_widget_child] 136 | c: Vec, 137 | }; 138 | 139 | impl Widget for Named { 140 | type UpdateAux = (); 141 | type GraphicalAux = (); 142 | type DisplayObject = (); 143 | } 144 | 145 | let mut unnamed = Unnamed(ExampleChild(0), ExampleChild(1), vec![ExampleChild(2)]); 146 | let mut named = Named { a: ExampleChild(2), b: ExampleChild(3), c: vec![ExampleChild(4)] }; 147 | 148 | assert_eq!(unnamed.children()[0].bounds().origin.x, 0.0); 149 | assert_eq!(unnamed.children_mut()[1].bounds().origin.x, 1.0); 150 | assert_eq!(unnamed.children()[2].bounds().origin.x, 2.0); 151 | 152 | assert_eq!(named.children_mut()[0].bounds().origin.x, 2.0); 153 | assert_eq!(named.children()[1].bounds().origin.x, 3.0); 154 | assert_eq!(named.children_mut()[2].bounds().origin.x, 4.0); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | use_small_heuristics = "Max" -------------------------------------------------------------------------------- /verbgraph/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "reclutch_verbgraph" 3 | version = "0.0.0" 4 | authors = ["jazzfool "] 5 | edition = "2018" 6 | license = "MIT / Apache-2.0" 7 | description = "A system to build safe event graphs with non-linear execution dependencies" 8 | homepage = "http://github.com/jazzfool/reclutch/tree/master/verbtree" 9 | repository = "http://github.com/jazzfool/reclutch" 10 | 11 | [dependencies] 12 | as-any = "0.2" 13 | paste = "0.1" 14 | reclutch_core = { path = "../core" } 15 | -------------------------------------------------------------------------------- /verbgraph/src/lib.rs: -------------------------------------------------------------------------------- 1 | use { 2 | as_any::{AsAny, Downcast}, 3 | reclutch_core::event::prelude::*, 4 | std::{collections::HashMap, ops::Deref}, 5 | }; 6 | 7 | pub use paste; 8 | pub use as_any; 9 | 10 | /// An object which contains an `OptionVerbGraph` that can be accessed mutably. 11 | pub trait HasVerbGraph: reclutch_core::widget::Widget + Sized + 'static { 12 | fn verb_graph(&mut self) -> &mut OptionVerbGraph; 13 | } 14 | 15 | /// An object which performs updates on it's own internal `VerbGraph`. 16 | pub trait OperatesVerbGraph: reclutch_core::widget::Widget { 17 | fn update_all(&mut self, additional: &mut Self::UpdateAux); 18 | fn require_update(&mut self, additional: &mut Self::UpdateAux, tag: &'static str); 19 | } 20 | 21 | /// Helper type alias; `VerbGraph` is commonly stored in an `Option` to allow 22 | /// referencing it's outer widget without violating borrow rules. 23 | pub type OptionVerbGraph = Option>; 24 | 25 | /// Event which returns a string corresponding to the current event variant. 26 | pub trait Event: Clone { 27 | fn get_key(&self) -> &'static str; 28 | } 29 | 30 | /// A queue handler not bound to any specific event queue. 31 | pub struct UnboundQueueHandler { 32 | handlers: HashMap<&'static str, Box>, 33 | } 34 | 35 | impl Default for UnboundQueueHandler { 36 | fn default() -> Self { 37 | UnboundQueueHandler { handlers: Default::default() } 38 | } 39 | } 40 | 41 | impl UnboundQueueHandler { 42 | /// Creates a new, unbound queue handler. 43 | pub fn new() -> Self { 44 | Default::default() 45 | } 46 | 47 | /// Adds a closure to be executed when an event of a specific key is matched. 48 | /// 49 | /// Also see [`event_key`](struct.Event.html#structmethod.get_key). 50 | pub fn on<'a>( 51 | &'a mut self, 52 | ev: &'static str, 53 | handler: impl Fn(&mut T, &mut A, E) + 'static, 54 | ) -> &'a mut Self { 55 | self.handlers.insert(ev, Box::new(handler)); 56 | self 57 | } 58 | 59 | /// Same as [`on`](UnboundQueueHandler::on), however `self` is consumed and returned. 60 | #[inline] 61 | pub fn and_on( 62 | mut self, 63 | ev: &'static str, 64 | handler: impl Fn(&mut T, &mut A, E) + 'static, 65 | ) -> Self { 66 | self.on(ev, handler); 67 | self 68 | } 69 | 70 | /// Binds the queue handler to a given event queue, thereby returning a regular, bound queue handler. 71 | pub fn bind, L: EventListen>( 72 | self, 73 | queue: &impl Deref, 74 | ) -> QueueHandler { 75 | QueueHandler { handlers: self.handlers, listener: queue.listen() } 76 | } 77 | } 78 | 79 | /// A queue handler containing a map of event keys to closures, bound to an event. 80 | pub struct QueueHandler> { 81 | handlers: HashMap<&'static str, Box>, 82 | listener: L, 83 | } 84 | 85 | impl> QueueHandler { 86 | /// Creates a new queue handler, listening to a given event queue. 87 | pub fn new>( 88 | queue: &impl Deref, 89 | ) -> Self { 90 | QueueHandler { handlers: HashMap::new(), listener: queue.listen() } 91 | } 92 | 93 | /// Adds a closure to be executed when an event of a specific key is matched. 94 | /// 95 | /// Also see [`event_key`](struct.Event.html#structmethod.get_key). 96 | pub fn on<'a>( 97 | &'a mut self, 98 | ev: &'static str, 99 | handler: impl Fn(&mut T, &mut A, E) + 'static, 100 | ) -> &'a mut Self { 101 | self.handlers.insert(ev, Box::new(handler)); 102 | self 103 | } 104 | 105 | /// Same as [`on`](QueueHandler::on), however `self` is consumed and returned. 106 | #[inline] 107 | pub fn and_on( 108 | mut self, 109 | ev: &'static str, 110 | handler: impl Fn(&mut T, &mut A, E) + 'static, 111 | ) -> Self { 112 | self.on(ev, handler); 113 | self 114 | } 115 | } 116 | 117 | /// Implemented by queue handlers to execute the inner closures regardless of surrounding types. 118 | pub trait DynQueueHandler: AsAny { 119 | /// Invokes the queue handler to peek events and match them. 120 | fn update(&mut self, obj: &mut T, additional: &mut A); 121 | /// Almost identical to `update`, however only the first `n` events are handled. 122 | fn update_n(&mut self, n: usize, obj: &mut T, additional: &mut A); 123 | } 124 | 125 | impl Downcast for dyn DynQueueHandler {} 126 | 127 | impl DynQueueHandler for QueueHandler 128 | where 129 | T: 'static, 130 | A: 'static, 131 | E: Event + 'static, 132 | L: EventListen + 'static, 133 | { 134 | fn update(&mut self, obj: &mut T, additional: &mut A) { 135 | let handlers = &mut self.handlers; 136 | self.listener.with(|events| { 137 | for event in events { 138 | if let Some(handler) = handlers.get_mut(event.get_key()) { 139 | (*handler)(obj, additional, event.clone()); 140 | } 141 | } 142 | }); 143 | } 144 | 145 | fn update_n(&mut self, n: usize, obj: &mut T, additional: &mut A) { 146 | let handlers = &mut self.handlers; 147 | self.listener.with_n(n, |events| { 148 | for event in events { 149 | if let Some(handler) = handlers.get_mut(event.get_key()) { 150 | (*handler)(obj, additional, event.clone()); 151 | } 152 | } 153 | }); 154 | } 155 | } 156 | 157 | /// Stores a list of queue handlers mapped to tags. 158 | /// The tags facilitate jumping to specifc sections of other `VerbGraph`s, hence allowing for non-linear queue handling. 159 | pub struct VerbGraph { 160 | handlers: HashMap<&'static str, Vec>>>, 161 | } 162 | 163 | impl Default for VerbGraph { 164 | fn default() -> Self { 165 | VerbGraph { handlers: Default::default() } 166 | } 167 | } 168 | 169 | impl VerbGraph { 170 | /// Creates a new, empty verb graph. 171 | /// Synonymous to `Default::default()`. 172 | #[inline] 173 | pub fn new() -> Self { 174 | Default::default() 175 | } 176 | 177 | /// Adds a queue handler, associated with a tag. 178 | pub fn add<'a, E: Event + 'static, L: EventListen + 'static>( 179 | &'a mut self, 180 | tag: &'static str, 181 | handler: QueueHandler, 182 | ) -> &'a mut Self { 183 | self.handlers.entry(tag).or_default().push(Box::new(handler)); 184 | self 185 | } 186 | 187 | /// Same as [`add`](VerbGraph::add), however `self` is consumed and returned. 188 | #[inline] 189 | pub fn and_add + 'static>( 190 | mut self, 191 | tag: &'static str, 192 | handler: QueueHandler, 193 | ) -> Self { 194 | self.add(tag, handler); 195 | self 196 | } 197 | 198 | fn update_handlers( 199 | handlers: &mut [Box>], 200 | obj: &mut T, 201 | additional: &mut A, 202 | ) { 203 | for handler in handlers { 204 | handler.update(obj, additional); 205 | } 206 | } 207 | 208 | /// Invokes all the queue handlers in a linear fashion, however non-linear jumping between verb graphs is still supported. 209 | pub fn update_all(&mut self, obj: &mut T, additional: &mut A) { 210 | for handler_list in self.handlers.values_mut() { 211 | VerbGraph::update_handlers(handler_list, obj, additional) 212 | } 213 | } 214 | 215 | /// Invokes the queue handlers for a specific tag. 216 | #[inline] 217 | pub fn update_tag(&mut self, obj: &mut T, additional: &mut A, tag: &'static str) { 218 | if let Some(handlers) = self.handlers.get_mut(tag) { 219 | VerbGraph::update_handlers(handlers, obj, additional) 220 | } 221 | } 222 | } 223 | 224 | fn update_obj_with(obj: &mut T, additional: &mut A, f: F) 225 | where 226 | T: HasVerbGraph, 227 | A: 'static, 228 | F: FnOnce(&mut VerbGraph, &mut T, &mut A), 229 | { 230 | if let Some(mut graph) = obj.verb_graph().take() { 231 | f(&mut graph, obj, additional); 232 | *obj.verb_graph() = Some(graph); 233 | } 234 | } 235 | 236 | /// Invokes the queue handler for a specific tag on a given object containing a verb graph. 237 | #[inline] 238 | pub fn require_update(obj: &mut T, additional: &mut A, tag: &'static str) 239 | where 240 | T: HasVerbGraph, 241 | A: 'static, 242 | { 243 | update_obj_with(obj, additional, |graph, obj, additional| { 244 | graph.update_tag(obj, additional, tag) 245 | }); 246 | } 247 | 248 | /// Invokes the queue handler for all tags on a given object containing a verb graph. 249 | #[inline] 250 | pub fn update_all(obj: &mut T, additional: &mut A) 251 | where 252 | T: HasVerbGraph, 253 | A: 'static, 254 | { 255 | update_obj_with(obj, additional, VerbGraph::update_all); 256 | } 257 | 258 | /// Simplifies the syntax of creating a verb graph. 259 | /// Example usage: 260 | /// ```rust,ignore 261 | /// reclutch_verbgraph::verbgraph! { 262 | /// SomeObject as obj, 263 | /// Aux as aux, 264 | /// 265 | /// "tag" => event in &event_queue => { 266 | /// event_key => { 267 | /// println!("Handling 'event_key' for event `event_queue`, under the tag `tag`"); 268 | /// } 269 | /// } 270 | /// } 271 | /// ``` 272 | /// Expands to: 273 | /// ```rust,ignore 274 | /// VerbGraph::new().add( 275 | /// "tag", 276 | /// QueueHandler::new(&event_queue).on( 277 | /// "event_key", 278 | /// |obj: &mut SomeObject, aux: &mut Aux, event| { 279 | /// let event = event.unwrap_as_event_key(); 280 | /// { 281 | /// println!("Handling 'event_key' for event in 'event_queue' under the tag 'tag'"); 282 | /// } 283 | /// }, 284 | /// ), 285 | /// ) 286 | /// ``` 287 | #[macro_export] 288 | macro_rules! verbgraph { 289 | ($ot:ty as $obj:ident,$at:ty as $add:ident, $($tag:expr => $eo:ident in $eq:expr=> {$($ev:tt => $body:block)*})*) => {{ 290 | let mut graph = $crate::VerbGraph::new(); 291 | $( 292 | let mut qh = $crate::QueueHandler::new($eq); 293 | $( 294 | qh.on( 295 | std::stringify!($ev), 296 | |$obj: &mut $ot, $add: &mut $at, #[allow(unused_variables)] $eo| { 297 | #[allow(unused_variables)] 298 | $crate::paste::expr!{ 299 | let $eo = $eo.[]().unwrap(); 300 | $body 301 | } 302 | }); 303 | )* 304 | graph.add($tag, qh); 305 | )* 306 | graph 307 | }}; 308 | } 309 | 310 | /// Simplifies the syntax of creating an unbound queue handler. 311 | /// 312 | /// # Example 313 | /// ```ignore 314 | /// unbound_queue_handler! { 315 | /// SomeObject as obj, 316 | /// Aux as aux, 317 | /// EventType as event, 318 | /// 319 | /// event_key => { 320 | /// println!("Handling 'event_key' for an unknown event queue"); 321 | /// } 322 | /// } 323 | /// ``` 324 | /// Expands to: 325 | /// ```ignore 326 | /// UnboundQueueHandler::new().on( 327 | /// |obj: &mut SomeObject, aux: &mut Aux, event: EventType, ctxt| { 328 | /// let event = event.unwrap_as_event_key().unwrap(); 329 | /// { 330 | /// println!("Handling 'event_key' for an unknown event queue"); 331 | /// } 332 | /// }, 333 | /// ) 334 | /// ``` 335 | #[macro_export] 336 | macro_rules! unbound_queue_handler { 337 | ($ot:ty as $obj:ident,$at:ty as $add:ident,$et:ty as $eo:ident,$($ev:tt => $body:block)*) => {{ 338 | let mut qh = $crate::UnboundQueueHandler::new(); 339 | $( 340 | qh.on( 341 | std::stringify!($ev), 342 | |$obj: &mut $ot, #[allow(unused_variables)] $add: &mut $at, $eo: $et| { 343 | #[allow(unused_variables)] 344 | $crate::paste::expr!{ 345 | let $eo = $eo.[]().unwrap(); 346 | $body 347 | } 348 | }); 349 | )* 350 | qh 351 | }} 352 | } 353 | 354 | #[cfg(test)] 355 | mod tests { 356 | use {super::*, reclutch_core::event::RcEventQueue}; 357 | 358 | #[test] 359 | fn test_jumping() { 360 | #[derive(Clone)] 361 | struct EmptyEvent; 362 | 363 | impl Event for EmptyEvent { 364 | fn get_key(&self) -> &'static str { 365 | "empty" 366 | } 367 | } 368 | 369 | impl EmptyEvent { 370 | fn unwrap_as_empty(self) -> Option<()> { 371 | Some(()) 372 | } 373 | } 374 | 375 | #[derive(Default)] 376 | struct Dependency { 377 | a: i32, 378 | b: i32, 379 | q: RcEventQueue, 380 | g: OptionVerbGraph, 381 | } 382 | 383 | impl reclutch_core::widget::Widget for Dependency { 384 | type UpdateAux = (); 385 | type GraphicalAux = (); 386 | type DisplayObject = (); 387 | } 388 | 389 | impl HasVerbGraph for Dependency { 390 | fn verb_graph(&mut self) -> &mut OptionVerbGraph { 391 | &mut self.g 392 | } 393 | } 394 | 395 | #[derive(Default)] 396 | struct Root { 397 | dep: Dependency, 398 | q: RcEventQueue, 399 | } 400 | 401 | let mut root = Root::default(); 402 | 403 | let mut root_graph = verbgraph! { 404 | Root as obj, 405 | () as aux, 406 | "_" => event in &root.q => { 407 | empty => { 408 | obj.dep.a += 1; 409 | obj.dep.q.emit_owned(EmptyEvent); 410 | require_update(&mut obj.dep, aux, "copy"); 411 | } 412 | } 413 | }; 414 | 415 | root.dep.g = verbgraph! { 416 | Dependency as obj, 417 | () as _aux, 418 | "copy" => event in &root.dep.q => { 419 | empty => { 420 | obj.b = obj.a; 421 | } 422 | } 423 | } 424 | .into(); 425 | 426 | for _ in 0..7 { 427 | root.q.emit_owned(EmptyEvent); 428 | } 429 | 430 | root_graph.update_all(&mut root, &mut ()); 431 | 432 | // without ever explicitly updating `root.dep.g`, `obj.a` should still be copied to `obj.b`. 433 | 434 | assert_eq!(root.dep.a, root.dep.b); 435 | assert_eq!(root.dep.b, 7); 436 | } 437 | } 438 | --------------------------------------------------------------------------------