1use std::any::Any;
4use std::fmt;
5use std::future::Future;
6use std::io;
7use std::pin::Pin;
8use std::sync::{Arc, Mutex, MutexGuard};
9use std::task::{Context, Poll};
10use std::time::{Duration, Instant};
11
12use futures_core::Stream;
13use urushi::Key;
14
15use super::delivery::{Admission, Sender};
16use super::effect::Mapper;
17use super::source::{Input, Signal, Surface};
18
19pub(crate) type ErasedMessage = Box<dyn Any + Send>;
21
22pub(crate) type BoxStream = Pin<Box<dyn Stream<Item = ErasedMessage> + Send + 'static>>;
24
25pub(crate) type AsyncStart = Box<dyn FnOnce(Sender<ErasedMessage>) -> BoxDone + Send + 'static>;
27
28pub(crate) type BlockingStart = Box<dyn FnOnce(Sender<ErasedMessage>) + Send + 'static>;
30
31type BoxDone = Pin<Box<dyn Future<Output = ()> + Send + 'static>>;
32
33pub struct Subscription<Message> {
64 sources: Vec<Source<Message>>,
65}
66
67pub(crate) struct Source<Message> {
69 pub(crate) key: Key,
70 pub(crate) kind: SourceKind<Message>,
71}
72
73pub(crate) enum SourceKind<Message> {
75 Input(Mapper<Input, Message>),
76 Surface(Mapper<Surface, Message>),
77 Interval {
78 period: Duration,
79 map: Mapper<Instant, Message>,
80 },
81 Signal {
82 signal: Signal,
83 map: Mapper<Signal, Message>,
84 },
85 TerminalErrors(Mapper<io::Error, Message>),
86 Stream {
87 admission: Admission,
88 stream: BoxStream,
89 map: DynamicMapper<Message>,
90 },
91 Run {
92 admission: Admission,
93 start: AsyncStart,
94 map: DynamicMapper<Message>,
95 },
96 RunBlocking {
97 admission: Admission,
98 start: BlockingStart,
99 map: DynamicMapper<Message>,
100 },
101}
102
103type ErasedMapper<Message> = Arc<dyn Fn(ErasedMessage) -> Message + Send + Sync>;
104
105pub(crate) struct DynamicMapper<Message> {
106 current: Arc<Mutex<ErasedMapper<Message>>>,
107}
108
109impl<Message> Clone for DynamicMapper<Message> {
110 fn clone(&self) -> Self {
111 Self {
112 current: Arc::clone(&self.current),
113 }
114 }
115}
116
117impl<Message: 'static> DynamicMapper<Message> {
118 fn identity() -> Self {
119 Self::new(|message| {
120 *message
121 .downcast::<Message>()
122 .expect("a subscription mapper receives its source message type")
123 })
124 }
125
126 fn new(map: impl Fn(ErasedMessage) -> Message + Send + Sync + 'static) -> Self {
127 Self {
128 current: Arc::new(Mutex::new(Arc::new(map))),
129 }
130 }
131
132 pub(crate) fn apply(&self, message: ErasedMessage) -> Message {
133 let map = Arc::clone(&lock(&self.current));
134 map(message)
135 }
136
137 pub(crate) fn replace(&self, newer: &Self) {
138 let newer = Arc::clone(&lock(&newer.current));
139 *lock(&self.current) = newer;
140 }
141
142 fn map<To>(self, map: Mapper<Message, To>) -> DynamicMapper<To>
143 where
144 To: 'static,
145 {
146 DynamicMapper::new(move |message| map(self.apply(message)))
147 }
148}
149
150#[derive(Hash)]
153enum RuntimeSource {
154 Input,
155 Surface,
156 TerminalErrors,
157}
158
159#[derive(Hash)]
165struct IntervalKey(Key, Duration);
166
167#[derive(Hash)]
169struct SignalKey(Signal);
170
171impl<Message> Subscription<Message> {
172 pub fn none() -> Self {
174 Self {
175 sources: Vec::new(),
176 }
177 }
178
179 pub fn input<F>(f: F) -> Self
185 where
186 F: Fn(Input) -> Message + Send + Sync + 'static,
187 {
188 Self::one(
189 Key::of(&RuntimeSource::Input),
190 SourceKind::Input(Arc::new(f)),
191 )
192 }
193
194 pub fn surface<F>(f: F) -> Self
196 where
197 F: Fn(Surface) -> Message + Send + Sync + 'static,
198 {
199 Self::one(
200 Key::of(&RuntimeSource::Surface),
201 SourceKind::Surface(Arc::new(f)),
202 )
203 }
204
205 pub fn interval<F>(key: impl Into<Key>, period: Duration, f: F) -> Self
215 where
216 F: Fn(Instant) -> Message + Send + Sync + 'static,
217 {
218 Self::one(
219 Key::of(&IntervalKey(key.into(), period)),
220 SourceKind::Interval {
221 period,
222 map: Arc::new(f),
223 },
224 )
225 }
226
227 pub fn signal<F>(signal: Signal, f: F) -> Self
231 where
232 F: Fn(Signal) -> Message + Send + Sync + 'static,
233 {
234 Self::one(
235 Key::of(&SignalKey(signal)),
236 SourceKind::Signal {
237 signal,
238 map: Arc::new(f),
239 },
240 )
241 }
242
243 pub fn terminal_errors<F>(f: F) -> Self
250 where
251 F: Fn(io::Error) -> Message + Send + Sync + 'static,
252 {
253 Self::one(
254 Key::of(&RuntimeSource::TerminalErrors),
255 SourceKind::TerminalErrors(Arc::new(f)),
256 )
257 }
258
259 pub fn stream<S>(key: impl Into<Key>, stream: S) -> Self
262 where
263 S: Stream<Item = Message> + Send + 'static,
264 Message: Send + 'static,
265 {
266 Self::stream_with(key, Admission::default(), stream)
267 }
268
269 pub fn stream_with<S>(key: impl Into<Key>, admission: Admission, stream: S) -> Self
271 where
272 S: Stream<Item = Message> + Send + 'static,
273 Message: Send + 'static,
274 {
275 Self::one(
276 key.into(),
277 SourceKind::Stream {
278 admission,
279 stream: Box::pin(EraseStream {
280 stream: Box::pin(stream),
281 }),
282 map: DynamicMapper::identity(),
283 },
284 )
285 }
286
287 pub fn run<F, Fut>(key: impl Into<Key>, f: F) -> Self
289 where
290 F: FnOnce(Sender<Message>) -> Fut + Send + 'static,
291 Fut: Future<Output = ()> + Send + 'static,
292 Message: Send + 'static,
293 {
294 Self::run_with(key, Admission::default(), f)
295 }
296
297 pub fn run_with<F, Fut>(key: impl Into<Key>, admission: Admission, f: F) -> Self
299 where
300 F: FnOnce(Sender<Message>) -> Fut + Send + 'static,
301 Fut: Future<Output = ()> + Send + 'static,
302 Message: Send + 'static,
303 {
304 let start: AsyncStart =
305 Box::new(move |sender| Box::pin(f(sender.contramap(Arc::new(box_message::<Message>)))));
306 Self::one(
307 key.into(),
308 SourceKind::Run {
309 admission,
310 start,
311 map: DynamicMapper::identity(),
312 },
313 )
314 }
315
316 pub fn run_blocking<F>(key: impl Into<Key>, f: F) -> Self
319 where
320 F: FnOnce(Sender<Message>) + Send + 'static,
321 Message: Send + 'static,
322 {
323 Self::run_blocking_with(key, Admission::default(), f)
324 }
325
326 pub fn run_blocking_with<F>(key: impl Into<Key>, admission: Admission, f: F) -> Self
329 where
330 F: FnOnce(Sender<Message>) + Send + 'static,
331 Message: Send + 'static,
332 {
333 Self::one(
334 key.into(),
335 SourceKind::RunBlocking {
336 admission,
337 start: Box::new(move |sender| {
338 f(sender.contramap(Arc::new(box_message::<Message>)));
339 }),
340 map: DynamicMapper::identity(),
341 },
342 )
343 }
344
345 pub fn batch(subscriptions: impl IntoIterator<Item = Self>) -> Self {
347 Self {
348 sources: subscriptions
349 .into_iter()
350 .flat_map(|subscription| subscription.sources)
351 .collect(),
352 }
353 }
354
355 pub fn map<To>(self, f: impl Fn(Message) -> To + Send + Sync + 'static) -> Subscription<To>
360 where
361 Message: Send + 'static,
362 To: Send + 'static,
363 {
364 let f: Mapper<Message, To> = Arc::new(f);
365 Subscription {
366 sources: self
367 .sources
368 .into_iter()
369 .map(|source| source.map_with(Arc::clone(&f)))
370 .collect(),
371 }
372 }
373
374 fn one(key: Key, kind: SourceKind<Message>) -> Self {
375 Self {
376 sources: vec![Source { key, kind }],
377 }
378 }
379
380 pub(crate) fn into_sources(self) -> Vec<Source<Message>> {
382 self.sources
383 }
384
385 pub(crate) fn terminal_error_mapper(&self) -> Option<Mapper<io::Error, Message>> {
386 self.sources.iter().rev().find_map(|source| {
387 if let SourceKind::TerminalErrors(mapper) = &source.kind {
388 Some(Arc::clone(mapper))
389 } else {
390 None
391 }
392 })
393 }
394}
395
396impl<Message> Source<Message> {
397 fn map_with<To>(self, f: Mapper<Message, To>) -> Source<To>
398 where
399 Message: Send + 'static,
400 To: Send + 'static,
401 {
402 let kind = match self.kind {
403 SourceKind::Input(map) => SourceKind::Input(compose(map, f)),
404 SourceKind::Surface(map) => SourceKind::Surface(compose(map, f)),
405 SourceKind::Interval { period, map } => SourceKind::Interval {
406 period,
407 map: compose(map, f),
408 },
409 SourceKind::Signal { signal, map } => SourceKind::Signal {
410 signal,
411 map: compose(map, f),
412 },
413 SourceKind::TerminalErrors(map) => SourceKind::TerminalErrors(compose(map, f)),
414 SourceKind::Stream {
415 admission,
416 stream,
417 map,
418 } => SourceKind::Stream {
419 admission,
420 stream,
421 map: map.map(Arc::clone(&f)),
422 },
423 SourceKind::Run {
424 admission,
425 start,
426 map,
427 } => SourceKind::Run {
428 admission,
429 start,
430 map: map.map(Arc::clone(&f)),
431 },
432 SourceKind::RunBlocking {
433 admission,
434 start,
435 map,
436 } => SourceKind::RunBlocking {
437 admission,
438 start,
439 map: map.map(f),
440 },
441 };
442 Source {
443 key: self.key,
444 kind,
445 }
446 }
447}
448
449fn compose<From, Mid, To>(first: Mapper<From, Mid>, second: Mapper<Mid, To>) -> Mapper<From, To>
450where
451 From: 'static,
452 Mid: 'static,
453 To: 'static,
454{
455 Arc::new(move |value| second(first(value)))
456}
457
458fn box_message<Message: Send + 'static>(message: Message) -> ErasedMessage {
459 Box::new(message)
460}
461
462struct EraseStream<S> {
463 stream: Pin<Box<S>>,
464}
465
466impl<S, Message> Stream for EraseStream<S>
467where
468 S: Stream<Item = Message>,
469 Message: Send + 'static,
470{
471 type Item = ErasedMessage;
472
473 fn poll_next(self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
474 self.get_mut()
475 .stream
476 .as_mut()
477 .poll_next(context)
478 .map(|item| item.map(box_message))
479 }
480}
481
482fn lock<T>(mutex: &Mutex<T>) -> MutexGuard<'_, T> {
483 mutex
484 .lock()
485 .unwrap_or_else(std::sync::PoisonError::into_inner)
486}
487
488impl<Message> Default for Subscription<Message> {
489 fn default() -> Self {
490 Self::none()
491 }
492}
493
494impl<Message> fmt::Debug for Subscription<Message> {
495 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
496 formatter.debug_list().entries(self.sources.iter()).finish()
497 }
498}
499
500impl<Message> fmt::Debug for Source<Message> {
501 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
502 formatter
503 .debug_struct("Source")
504 .field("key", &self.key)
505 .field("kind", &self.kind.name())
506 .finish()
507 }
508}
509
510impl<Message> SourceKind<Message> {
511 pub(crate) fn is_surface(&self) -> bool {
512 matches!(self, Self::Surface(_))
513 }
514
515 pub(crate) fn name(&self) -> &'static str {
516 match self {
517 Self::Input(_) => "input",
518 Self::Surface(_) => "surface",
519 Self::Interval { .. } => "interval",
520 Self::Signal { .. } => "signal",
521 Self::TerminalErrors(_) => "terminal_errors",
522 Self::Stream { .. } => "stream",
523 Self::Run { .. } => "run",
524 Self::RunBlocking { .. } => "run_blocking",
525 }
526 }
527}
528
529#[cfg(test)]
530#[path = "subscription_tests.rs"]
531mod tests;