diff options
author | 2019-12-14 05:56:46 +0100 | |
---|---|---|
committer | 2019-12-14 05:56:46 +0100 | |
commit | d6c3da21f7fe7a79bcfbc2a180dc111e42300a04 (patch) | |
tree | bbe8e02712824cd0fe22028bd3072fbfce104d62 | |
parent | 293314405f5b8d4003db5ef8f428e659ae36872d (diff) | |
download | iced-d6c3da21f7fe7a79bcfbc2a180dc111e42300a04.tar.gz iced-d6c3da21f7fe7a79bcfbc2a180dc111e42300a04.tar.bz2 iced-d6c3da21f7fe7a79bcfbc2a180dc111e42300a04.zip |
Write docs for subscriptions and reorganize a bit
-rw-r--r-- | core/src/command.rs | 4 | ||||
-rw-r--r-- | core/src/lib.rs | 2 | ||||
-rw-r--r-- | core/src/subscription.rs | 65 | ||||
-rw-r--r-- | examples/events.rs | 2 | ||||
-rw-r--r-- | examples/stopwatch.rs | 2 | ||||
-rw-r--r-- | native/src/lib.rs | 2 | ||||
-rw-r--r-- | native/src/subscription.rs | 30 | ||||
-rw-r--r-- | src/application.rs | 17 | ||||
-rw-r--r-- | src/sandbox.rs | 2 | ||||
-rw-r--r-- | winit/src/application.rs | 124 | ||||
-rw-r--r-- | winit/src/lib.rs | 1 | ||||
-rw-r--r-- | winit/src/subscription.rs | 97 |
12 files changed, 225 insertions, 123 deletions
diff --git a/core/src/command.rs b/core/src/command.rs index 14b48b5b..e0e5ab5c 100644 --- a/core/src/command.rs +++ b/core/src/command.rs @@ -34,8 +34,8 @@ impl<T> Command<T> { } } - /// Creates a [`Command`] that performs the actions of all the givens - /// futures. + /// Creates a [`Command`] that performs the actions of all the given + /// commands. /// /// Once this command is run, all the futures will be exectued at once. /// diff --git a/core/src/lib.rs b/core/src/lib.rs index 6f13c310..821b09c1 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -9,7 +9,7 @@ //! [Iced]: https://github.com/hecrj/iced //! [`iced_native`]: https://github.com/hecrj/iced/tree/master/native //! [`iced_web`]: https://github.com/hecrj/iced/tree/master/web -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] diff --git a/core/src/subscription.rs b/core/src/subscription.rs index 21868c20..8de6cae8 100644 --- a/core/src/subscription.rs +++ b/core/src/subscription.rs @@ -1,6 +1,21 @@ -//! Generate events asynchronously for you application. - -/// An event subscription. +//! Listen to external events in your application. + +/// A request to listen to external events. +/// +/// Besides performing async actions on demand with [`Command`], most +/// applications also need to listen to external events passively. +/// +/// A [`Subscription`] is normally provided to some runtime, like a [`Command`], +/// and it will generate events as long as the user keeps requesting it. +/// +/// For instance, you can use a [`Subscription`] to listen to a WebSocket +/// connection, keyboard presses, mouse events, time ticks, etc. +/// +/// This type is normally aliased by runtimes with a specific `Input` and/or +/// `Hasher`. +/// +/// [`Command`]: ../struct.Command.html +/// [`Subscription`]: struct.Subscription.html pub struct Subscription<Hasher, Input, Output> { recipes: Vec<Box<dyn Recipe<Hasher, Input, Output = Output>>>, } @@ -9,12 +24,19 @@ impl<H, I, O> Subscription<H, I, O> where H: std::hash::Hasher, { + /// Returns an empty [`Subscription`] that will not produce any output. + /// + /// [`Subscription`]: struct.Subscription.html pub fn none() -> Self { Self { recipes: Vec::new(), } } + /// Creates a [`Subscription`] from a [`Recipe`] describing it. + /// + /// [`Subscription`]: struct.Subscription.html + /// [`Recipe`]: trait.Recipe.html pub fn from_recipe( recipe: impl Recipe<H, I, Output = O> + 'static, ) -> Self { @@ -23,6 +45,10 @@ where } } + /// Batches all the provided subscriptions and returns the resulting + /// [`Subscription`]. + /// + /// [`Subscription`]: struct.Subscription.html pub fn batch( subscriptions: impl Iterator<Item = Subscription<H, I, O>>, ) -> Self { @@ -33,10 +59,16 @@ where } } + /// Returns the different recipes of the [`Subscription`]. + /// + /// [`Subscription`]: struct.Subscription.html pub fn recipes(self) -> Vec<Box<dyn Recipe<H, I, Output = O>>> { self.recipes } + /// Transforms the [`Subscription`] output with the given function. + /// + /// [`Subscription`]: struct.Subscription.html pub fn map<A>( mut self, f: impl Fn(O) -> A + Send + Sync + 'static, @@ -68,12 +100,37 @@ impl<I, O, H> std::fmt::Debug for Subscription<I, O, H> { } } -/// The connection of an event subscription. +/// The description of a [`Subscription`]. +/// +/// A [`Recipe`] is the internal definition of a [`Subscription`]. It is used +/// by runtimes to run and identify subscriptions. You can use it to create your +/// own! +/// +/// [`Subscription`]: struct.Subscription.html +/// [`Recipe`]: trait.Recipe.html pub trait Recipe<Hasher: std::hash::Hasher, Input> { + /// The events that will be produced by a [`Subscription`] with this + /// [`Recipe`]. + /// + /// [`Subscription`]: struct.Subscription.html + /// [`Recipe`]: trait.Recipe.html type Output; + /// Hashes the [`Recipe`]. + /// + /// This is used by runtimes to uniquely identify a [`Subscription`]. + /// + /// [`Subscription`]: struct.Subscription.html + /// [`Recipe`]: trait.Recipe.html fn hash(&self, state: &mut Hasher); + /// Executes the [`Recipe`] and produces the stream of events of its + /// [`Subscription`]. + /// + /// It receives some generic `Input`, which is normally defined by runtimes. + /// + /// [`Subscription`]: struct.Subscription.html + /// [`Recipe`]: trait.Recipe.html fn stream( self: Box<Self>, input: Input, diff --git a/examples/events.rs b/examples/events.rs index 0b944495..7d83fbd8 100644 --- a/examples/events.rs +++ b/examples/events.rs @@ -47,7 +47,7 @@ impl Application for Events { Command::none() } - fn subscriptions(&self) -> Subscription<Message> { + fn subscription(&self) -> Subscription<Message> { if self.enabled { iced_native::subscription::events().map(Message::EventOccurred) } else { diff --git a/examples/stopwatch.rs b/examples/stopwatch.rs index 0d52a091..7a7f0793 100644 --- a/examples/stopwatch.rs +++ b/examples/stopwatch.rs @@ -74,7 +74,7 @@ impl Application for Stopwatch { Command::none() } - fn subscriptions(&self) -> Subscription<Message> { + fn subscription(&self) -> Subscription<Message> { match self.state { State::Idle => Subscription::none(), State::Ticking { .. } => { diff --git a/native/src/lib.rs b/native/src/lib.rs index af937a2f..c4d72df8 100644 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -34,7 +34,7 @@ //! [`Windowed`]: renderer/trait.Windowed.html //! [`UserInterface`]: struct.UserInterface.html //! [renderer]: renderer/index.html -//#![deny(missing_docs)] +#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![deny(unused_results)] #![deny(unsafe_code)] diff --git a/native/src/subscription.rs b/native/src/subscription.rs index c49e24d2..db88867a 100644 --- a/native/src/subscription.rs +++ b/native/src/subscription.rs @@ -1,16 +1,42 @@ +//! Listen to external events in your application. use crate::{Event, Hasher}; use futures::stream::BoxStream; -pub type EventStream = BoxStream<'static, Event>; - +/// A request to listen to external events. +/// +/// Besides performing async actions on demand with [`Command`], most +/// applications also need to listen to external events passively. +/// +/// A [`Subscription`] is normally provided to some runtime, like a [`Command`], +/// and it will generate events as long as the user keeps requesting it. +/// +/// For instance, you can use a [`Subscription`] to listen to a WebSocket +/// connection, keyboard presses, mouse events, time ticks, etc. +/// +/// [`Command`]: ../struct.Command.html +/// [`Subscription`]: struct.Subscription.html pub type Subscription<T> = iced_core::Subscription<Hasher, EventStream, T>; +/// A stream of runtime events. +/// +/// It is the input of a [`Subscription`] in the native runtime. +/// +/// [`Subscription`]: type.Subscription.html +pub type EventStream = BoxStream<'static, Event>; + pub use iced_core::subscription::Recipe; mod events; use events::Events; +/// Returns a [`Subscription`] to all the runtime events. +/// +/// This subscription will notify your application of any [`Event`] handled by +/// the runtime. +/// +/// [`Subscription`]: type.Subscription.html +/// [`Event`]: ../enum.Event.html pub fn events() -> Subscription<Event> { Subscription::from_recipe(Events) } diff --git a/src/application.rs b/src/application.rs index 95113344..98e160ce 100644 --- a/src/application.rs +++ b/src/application.rs @@ -117,8 +117,17 @@ pub trait Application: Sized { /// [`Command`]: struct.Command.html fn update(&mut self, message: Self::Message) -> Command<Self::Message>; - /// TODO - fn subscriptions(&self) -> Subscription<Self::Message> { + /// Returns the event [`Subscription`] for the current state of the + /// application. + /// + /// A [`Subscription`] will be kept alive as long as you keep returning it, + /// and the __messages__ produced will be handled by + /// [`update`](#tymethod.update). + /// + /// By default, this method returns an empty [`Subscription`]. + /// + /// [`Subscription`]: struct.Subscription.html + fn subscription(&self) -> Subscription<Self::Message> { Subscription::none() } @@ -173,8 +182,8 @@ where self.0.update(message) } - fn subscriptions(&self) -> Subscription<Self::Message> { - self.0.subscriptions() + fn subscription(&self) -> Subscription<Self::Message> { + self.0.subscription() } fn view(&mut self) -> Element<'_, Self::Message> { diff --git a/src/sandbox.rs b/src/sandbox.rs index 248aa152..75020b16 100644 --- a/src/sandbox.rs +++ b/src/sandbox.rs @@ -149,7 +149,7 @@ where Command::none() } - fn subscriptions(&self) -> Subscription<T::Message> { + fn subscription(&self) -> Subscription<T::Message> { Subscription::none() } diff --git a/winit/src/application.rs b/winit/src/application.rs index 2d30e9f3..3b8ac16b 100644 --- a/winit/src/application.rs +++ b/winit/src/application.rs @@ -2,10 +2,9 @@ use crate::{ conversion, input::{keyboard, mouse}, renderer::{Target, Windowed}, - Cache, Command, Container, Debug, Element, Event, Hasher, Length, + subscription, Cache, Command, Container, Debug, Element, Event, Length, MouseCursor, Settings, Subscription, UserInterface, }; -use std::collections::HashMap; /// An interactive, native cross-platform application. /// @@ -58,8 +57,14 @@ pub trait Application: Sized { /// [`Command`]: struct.Command.html fn update(&mut self, message: Self::Message) -> Command<Self::Message>; - /// TODO - fn subscriptions(&self) -> Subscription<Self::Message>; + /// Returns the event `Subscription` for the current state of the + /// application. + /// + /// The messages produced by the `Subscription` will be handled by + /// [`update`](#tymethod.update). + /// + /// A `Subscription` will be kept alive as long as you keep returning it! + fn subscription(&self) -> Subscription<Self::Message>; /// Returns the widgets to display in the [`Application`]. /// @@ -93,14 +98,14 @@ pub trait Application: Sized { let proxy = event_loop.create_proxy(); let mut thread_pool = futures::executor::ThreadPool::new().expect("Create thread pool"); - let mut alive_subscriptions = Subscriptions::new(); + let mut subscription_pool = subscription::Pool::new(); let mut external_messages = Vec::new(); let (mut application, init_command) = Self::new(); spawn(init_command, &mut thread_pool, &proxy); - let subscriptions = application.subscriptions(); - alive_subscriptions.update(subscriptions, &mut thread_pool, &proxy); + let subscription = application.subscription(); + subscription_pool.update(subscription, &mut thread_pool, &proxy); let mut title = application.title(); @@ -184,9 +189,9 @@ pub trait Application: Sized { debug.layout_finished(); debug.event_processing_started(); - events - .iter() - .for_each(|event| alive_subscriptions.send_event(*event)); + events.iter().for_each(|event| { + subscription_pool.broadcast_event(*event) + }); let mut messages = user_interface.update(&renderer, events.drain(..)); @@ -215,9 +220,9 @@ pub trait Application: Sized { debug.update_finished(); } - let subscriptions = application.subscriptions(); - alive_subscriptions.update( - subscriptions, + let subscription = application.subscription(); + subscription_pool.update( + subscription, &mut thread_pool, &proxy, ); @@ -424,99 +429,6 @@ fn spawn<Message: Send>( } } -pub struct Subscriptions { - alive: HashMap<u64, Connection>, -} - -pub struct Connection { - _cancel: futures::channel::oneshot::Sender<()>, - listener: Option<futures::channel::mpsc::Sender<Event>>, -} - -impl Subscriptions { - fn new() -> Self { - Self { - alive: HashMap::new(), - } - } - - fn update<Message: Send>( - &mut self, - subscriptions: Subscription<Message>, - thread_pool: &mut futures::executor::ThreadPool, - proxy: &winit::event_loop::EventLoopProxy<Message>, - ) { - use futures::{future::FutureExt, stream::StreamExt}; - - let recipes = subscriptions.recipes(); - let mut alive = std::collections::HashSet::new(); - - for recipe in recipes { - let id = { - use std::hash::Hasher as _; - - let mut hasher = Hasher::default(); - recipe.hash(&mut hasher); - - hasher.finish() - }; - - let _ = alive.insert(id); - - if !self.alive.contains_key(&id) { - let (cancel, cancelled) = futures::channel::oneshot::channel(); - let (event_sender, event_receiver) = - futures::channel::mpsc::channel(100); - - let stream = recipe.stream(event_receiver.boxed()); - let proxy = proxy.clone(); - - let future = futures::future::select( - cancelled, - stream.for_each(move |message| { - proxy - .send_event(message) - .expect("Send subscription result to event loop"); - - futures::future::ready(()) - }), - ) - .map(|_| ()); - - thread_pool.spawn_ok(future); - - let _ = self.alive.insert( - id, - Connection { - _cancel: cancel, - listener: if event_sender.is_closed() { - None - } else { - Some(event_sender) - }, - }, - ); - } - } - - self.alive.retain(|id, _| alive.contains(&id)); - } - - fn send_event(&mut self, event: Event) { - self.alive - .values_mut() - .filter_map(|connection| connection.listener.as_mut()) - .for_each(|listener| { - if let Err(error) = listener.try_send(event) { - log::warn!( - "Error sending event to subscription: {:?}", - error - ); - } - }); - } -} - // As defined in: http://www.unicode.org/faq/private_use.html // TODO: Remove once https://github.com/rust-windowing/winit/pull/1254 lands fn is_private_use_character(c: char) -> bool { diff --git a/winit/src/lib.rs b/winit/src/lib.rs index df3a6997..8a1dc870 100644 --- a/winit/src/lib.rs +++ b/winit/src/lib.rs @@ -29,6 +29,7 @@ pub mod conversion; pub mod settings; mod application; +mod subscription; pub use application::Application; pub use settings::Settings; diff --git a/winit/src/subscription.rs b/winit/src/subscription.rs new file mode 100644 index 00000000..610bdc10 --- /dev/null +++ b/winit/src/subscription.rs @@ -0,0 +1,97 @@ +use iced_native::{Event, Hasher, Subscription}; +use std::collections::HashMap; + +pub struct Pool { + alive: HashMap<u64, Handle>, +} + +pub struct Handle { + _cancel: futures::channel::oneshot::Sender<()>, + listener: Option<futures::channel::mpsc::Sender<Event>>, +} + +impl Pool { + pub fn new() -> Self { + Self { + alive: HashMap::new(), + } + } + + pub fn update<Message: Send>( + &mut self, + subscription: Subscription<Message>, + thread_pool: &mut futures::executor::ThreadPool, + proxy: &winit::event_loop::EventLoopProxy<Message>, + ) { + use futures::{future::FutureExt, stream::StreamExt}; + + let recipes = subscription.recipes(); + let mut alive = std::collections::HashSet::new(); + + for recipe in recipes { + let id = { + use std::hash::Hasher as _; + + let mut hasher = Hasher::default(); + recipe.hash(&mut hasher); + + hasher.finish() + }; + + let _ = alive.insert(id); + + if !self.alive.contains_key(&id) { + let (cancel, cancelled) = futures::channel::oneshot::channel(); + + // TODO: Use bus if/when it supports async + let (event_sender, event_receiver) = + futures::channel::mpsc::channel(100); + + let stream = recipe.stream(event_receiver.boxed()); + let proxy = proxy.clone(); + + let future = futures::future::select( + cancelled, + stream.for_each(move |message| { + proxy + .send_event(message) + .expect("Send subscription result to event loop"); + + futures::future::ready(()) + }), + ) + .map(|_| ()); + + thread_pool.spawn_ok(future); + + let _ = self.alive.insert( + id, + Handle { + _cancel: cancel, + listener: if event_sender.is_closed() { + None + } else { + Some(event_sender) + }, + }, + ); + } + } + + self.alive.retain(|id, _| alive.contains(&id)); + } + + pub fn broadcast_event(&mut self, event: Event) { + self.alive + .values_mut() + .filter_map(|connection| connection.listener.as_mut()) + .for_each(|listener| { + if let Err(error) = listener.try_send(event) { + log::warn!( + "Error sending event to subscription: {:?}", + error + ); + } + }); + } +} |