summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2019-12-14 05:56:46 +0100
committerLibravatar Héctor Ramón Jiménez <hector0193@gmail.com>2019-12-14 05:56:46 +0100
commitd6c3da21f7fe7a79bcfbc2a180dc111e42300a04 (patch)
treebbe8e02712824cd0fe22028bd3072fbfce104d62
parent293314405f5b8d4003db5ef8f428e659ae36872d (diff)
downloadiced-d6c3da21f7fe7a79bcfbc2a180dc111e42300a04.tar.gz
iced-d6c3da21f7fe7a79bcfbc2a180dc111e42300a04.tar.bz2
iced-d6c3da21f7fe7a79bcfbc2a180dc111e42300a04.zip
Write docs for subscriptions and reorganize a bit
-rw-r--r--core/src/command.rs4
-rw-r--r--core/src/lib.rs2
-rw-r--r--core/src/subscription.rs65
-rw-r--r--examples/events.rs2
-rw-r--r--examples/stopwatch.rs2
-rw-r--r--native/src/lib.rs2
-rw-r--r--native/src/subscription.rs30
-rw-r--r--src/application.rs17
-rw-r--r--src/sandbox.rs2
-rw-r--r--winit/src/application.rs124
-rw-r--r--winit/src/lib.rs1
-rw-r--r--winit/src/subscription.rs97
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
+ );
+ }
+ });
+ }
+}