iced_futures/runtime.rs
1//! Run commands and keep track of subscriptions.
2use crate::subscription;
3use crate::{BoxFuture, BoxStream, Executor, MaybeSend};
4
5use futures::{Sink, channel::mpsc};
6use std::marker::PhantomData;
7
8/// A batteries-included runtime of commands and subscriptions.
9///
10/// If you have an [`Executor`], a [`Runtime`] can be leveraged to run any
11/// `Command` or [`Subscription`] and get notified of the results!
12///
13/// [`Subscription`]: crate::Subscription
14#[derive(Debug)]
15pub struct Runtime<Executor, Sender, Message> {
16 executor: Executor,
17 sender: Sender,
18 subscriptions: subscription::Tracker,
19 _message: PhantomData<Message>,
20}
21
22impl<Executor, Sender, Message> Runtime<Executor, Sender, Message>
23where
24 Executor: self::Executor,
25 Sender: Sink<Message, Error = mpsc::SendError>
26 + Unpin
27 + MaybeSend
28 + Clone
29 + 'static,
30 Message: MaybeSend + 'static,
31{
32 /// Creates a new empty [`Runtime`].
33 ///
34 /// You need to provide:
35 /// - an [`Executor`] to spawn futures
36 /// - a `Sender` implementing `Sink` to receive the results
37 pub fn new(executor: Executor, sender: Sender) -> Self {
38 Self {
39 executor,
40 sender,
41 subscriptions: subscription::Tracker::new(),
42 _message: PhantomData,
43 }
44 }
45
46 /// Runs the given closure inside the [`Executor`] of the [`Runtime`].
47 ///
48 /// See [`Executor::enter`] to learn more.
49 pub fn enter<R>(&self, f: impl FnOnce() -> R) -> R {
50 self.executor.enter(f)
51 }
52
53 /// Spawns a [`Future`] in the [`Runtime`].
54 ///
55 /// The resulting `Message` will be forwarded to the `Sender` of the
56 /// [`Runtime`].
57 ///
58 /// [`Future`]: BoxFuture
59 pub fn spawn(&mut self, future: BoxFuture<Message>) {
60 use futures::{FutureExt, SinkExt};
61
62 let mut sender = self.sender.clone();
63
64 let future = future.then(|message| async move {
65 let _ = sender.send(message).await;
66 });
67
68 self.executor.spawn(future);
69 }
70
71 /// Runs a [`Stream`] in the [`Runtime`] until completion.
72 ///
73 /// The resulting `Message`s will be forwarded to the `Sender` of the
74 /// [`Runtime`].
75 ///
76 /// [`Stream`]: BoxStream
77 pub fn run(&mut self, stream: BoxStream<Message>) {
78 use futures::{FutureExt, StreamExt};
79
80 let sender = self.sender.clone();
81 let future =
82 stream.map(Ok).forward(sender).map(|result| match result {
83 Ok(()) => (),
84 Err(error) => {
85 log::warn!(
86 "Stream could not run until completion: {error}"
87 );
88 }
89 });
90
91 self.executor.spawn(future);
92 }
93
94 /// Tracks a [`Subscription`] in the [`Runtime`].
95 ///
96 /// It will spawn new streams or close old ones as necessary! See
97 /// [`Tracker::update`] to learn more about this!
98 ///
99 /// [`Tracker::update`]: subscription::Tracker::update
100 /// [`Subscription`]: crate::Subscription
101 pub fn track(
102 &mut self,
103 recipes: impl IntoIterator<
104 Item = Box<dyn subscription::Recipe<Output = Message>>,
105 >,
106 ) {
107 let Runtime {
108 executor,
109 subscriptions,
110 sender,
111 ..
112 } = self;
113
114 let futures = executor.enter(|| {
115 subscriptions.update(recipes.into_iter(), sender.clone())
116 });
117
118 for future in futures {
119 executor.spawn(future);
120 }
121 }
122
123 /// Broadcasts an event to all the subscriptions currently alive in the
124 /// [`Runtime`].
125 ///
126 /// See [`Tracker::broadcast`] to learn more.
127 ///
128 /// [`Tracker::broadcast`]: subscription::Tracker::broadcast
129 pub fn broadcast(&mut self, event: subscription::Event) {
130 self.subscriptions.broadcast(event);
131 }
132}