msg type -> wire::push() -> update handle wire into module/service/ect
This commit is contained in:
@@ -1,48 +1,33 @@
|
||||
//! Clock service: emits a `ServiceEvent` whenever a clock kind's displayed
|
||||
//! value changes (minute or second rollover).
|
||||
//! Clock service: publishes a `Wire` on every change of the displayed
|
||||
//! value, and accepts `ClockKind` wires from modules that want it to switch
|
||||
//! which kind it publishes.
|
||||
//!
|
||||
//! The ticker knows nothing about the app's `Message` type; it produces
|
||||
//! protocol events from `common` that core routes to subscribed modules.
|
||||
//! protocol wires from `common` that core routes to subscribers.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use iced::{futures::SinkExt, Subscription};
|
||||
use iced::futures::{channel::mpsc, SinkExt, StreamExt};
|
||||
use iced::Subscription;
|
||||
|
||||
use common::{Service, ServiceEvent};
|
||||
use common::{ClockKind, ClockPayload, Endpoint, Inbox, Wire};
|
||||
|
||||
/// Route-key namespace owned by this service; keys are `"clock.<kind>"`.
|
||||
pub const NAMESPACE: &str = "clock.";
|
||||
|
||||
/// What a clock subscription wants: the tick interval and payload selector.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum ClockKind {
|
||||
Mins,
|
||||
Seconds,
|
||||
}
|
||||
|
||||
impl ClockKind {
|
||||
/// Route key a module subscribes under to receive this kind's ticks.
|
||||
pub fn key(self) -> Service {
|
||||
Service(match self {
|
||||
Self::Mins => "clock.mins",
|
||||
Self::Seconds => "clock.seconds",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Clock tick payload: the value plus the kind that produced it.
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub struct ClockPayload {
|
||||
pub kind: ClockKind,
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
/// Emits a payload only when a kind's displayed value changes (minute or
|
||||
/// second rollover). Drive `tick` once per second.
|
||||
#[derive(Default)]
|
||||
/// Drives the clock, publishing only the kind modules last asked for.
|
||||
pub struct ClockTicker {
|
||||
last: HashMap<ClockKind, String>,
|
||||
want: ClockKind,
|
||||
}
|
||||
|
||||
impl Default for ClockTicker {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
last: HashMap::new(),
|
||||
want: ClockKind::Seconds,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClockTicker {
|
||||
@@ -50,24 +35,41 @@ impl ClockTicker {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Subscription that emits a `ServiceEvent` on every change.
|
||||
pub fn run() -> Subscription<ServiceEvent> {
|
||||
Subscription::run_with((), |_| {
|
||||
/// Subscription that reads inbound wires from `inbox` and publishes
|
||||
/// changes to every module subscribed to this service's route key.
|
||||
pub fn run(inbox: Inbox) -> Subscription<Wire> {
|
||||
Subscription::run_with(inbox, |inbox| {
|
||||
let key = inbox.key();
|
||||
// ponytail: the builder runs once (identity is stable, see `Inbox`
|
||||
// hashing). If a module ever makes `services()` dynamic and a key
|
||||
// is dropped then recreated, `take()` returns None and this panics
|
||||
// — make `Bar` rebuild the inbox per start instead.
|
||||
let mut rx = inbox.take().expect("inbox receiver is taken once");
|
||||
iced::stream::channel(
|
||||
0,
|
||||
|mut sender: iced::futures::channel::mpsc::Sender<ServiceEvent>| async move {
|
||||
move |mut sender: mpsc::Sender<Wire>| async move {
|
||||
let mut clock = ClockTicker::new();
|
||||
let mut interval = tokio::time::interval(std::time::Duration::from_secs(1));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
for payload in clock.tick() {
|
||||
let event = ServiceEvent {
|
||||
key: payload.kind.key(),
|
||||
payload: Arc::new(payload),
|
||||
};
|
||||
if sender.send(event).await.is_err() {
|
||||
return;
|
||||
tokio::select! {
|
||||
_ = interval.tick() => {
|
||||
if let Some(payload) = clock.tick() {
|
||||
let wire = Wire::topic(Endpoint::service(key), key, payload);
|
||||
if sender.send(wire).await.is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
msg = rx.next() => match msg {
|
||||
Some(wire) => {
|
||||
// A module poking us: switch published kind.
|
||||
if let Some(kind) = wire.downcast::<ClockKind>() {
|
||||
clock.want = *kind;
|
||||
clock.last.clear();
|
||||
}
|
||||
}
|
||||
None => return,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -75,27 +77,21 @@ impl ClockTicker {
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns a payload per kind whose value changed since the last tick.
|
||||
/// The second that rolls into a new minute yields `[Seconds, Mins]`.
|
||||
pub fn tick(&mut self) -> Vec<ClockPayload> {
|
||||
let now = chrono::Local::now();
|
||||
[ClockKind::Seconds, ClockKind::Mins]
|
||||
.into_iter()
|
||||
.filter_map(|kind| {
|
||||
let value = now
|
||||
.format(match kind {
|
||||
ClockKind::Mins => "%H:%M",
|
||||
ClockKind::Seconds => "%H:%M:%S",
|
||||
})
|
||||
.to_string();
|
||||
match self.last.get(&kind) {
|
||||
Some(prev) if *prev == value => None,
|
||||
_ => {
|
||||
self.last.insert(kind, value.clone());
|
||||
Some(ClockPayload { kind, value })
|
||||
}
|
||||
}
|
||||
/// Latest value for the wanted kind, or `None` if unchanged.
|
||||
pub fn tick(&mut self) -> Option<ClockPayload> {
|
||||
let value = chrono::Local::now()
|
||||
.format(match self.want {
|
||||
ClockKind::Mins => "%H:%M",
|
||||
ClockKind::Seconds => "%H:%M:%S",
|
||||
})
|
||||
.collect()
|
||||
.to_string();
|
||||
if self.last.get(&self.want) == Some(&value) {
|
||||
return None;
|
||||
}
|
||||
self.last.insert(self.want, value.clone());
|
||||
Some(ClockPayload {
|
||||
kind: self.want,
|
||||
value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user