reorginise

This commit is contained in:
2026-09-10 02:08:45 +01:00
parent 8696425590
commit bdec970b62
20 changed files with 593 additions and 548 deletions
+28
View File
@@ -0,0 +1,28 @@
use common::Service;
/// 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,
}
+6 -68
View File
@@ -1,73 +1,11 @@
//! Clock service: emits a `ServicePayloadKind` event whenever a clock kind's
//! displayed value changes (minute or second rollover).
//! Clock service: emits a `ServiceEvent` whenever a clock kind's displayed
//! value changes (minute or second rollover).
//!
//! The ticker knows nothing about the app's `Message` type; it produces
//! protocol events from `common` that core routes to subscribed modules.
use std::collections::HashMap;
mod kind;
mod ticker;
use iced::{futures::SinkExt, Subscription};
use common::{ClockKind, ClockPayload, ServicePayloadKind};
/// Emits a payload only when a kind's displayed value changes (minute or
/// second rollover). Drive `tick` once per second.
#[derive(Default)]
pub struct ClockTicker {
last: HashMap<ClockKind, String>,
}
impl ClockTicker {
pub fn new() -> Self {
Self::default()
}
/// Subscription that emits `ServicePayloadKind::Clock` on every change.
pub fn run() -> Subscription<ServicePayloadKind> {
Subscription::run_with((), |_| {
iced::stream::channel(
0,
|mut sender: iced::futures::channel::mpsc::Sender<ServicePayloadKind>| 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() {
if sender
.send(ServicePayloadKind::Clock(payload))
.await
.is_err()
{
return;
}
}
}
},
)
})
}
/// 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 })
}
}
})
.collect()
}
}
pub use kind::{ClockKind, ClockPayload, NAMESPACE};
pub use ticker::ClockTicker;
+70
View File
@@ -0,0 +1,70 @@
use std::collections::HashMap;
use std::sync::Arc;
use iced::{futures::SinkExt, Subscription};
use common::ServiceEvent;
use crate::kind::{ClockKind, ClockPayload};
/// Emits a payload only when a kind's displayed value changes (minute or
/// second rollover). Drive `tick` once per second.
#[derive(Default)]
pub struct ClockTicker {
last: HashMap<ClockKind, String>,
}
impl ClockTicker {
pub fn new() -> Self {
Self::default()
}
/// Subscription that emits a `ServiceEvent` on every change.
pub fn run() -> Subscription<ServiceEvent> {
Subscription::run_with((), |_| {
iced::stream::channel(
0,
|mut sender: iced::futures::channel::mpsc::Sender<ServiceEvent>| 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;
}
}
}
},
)
})
}
/// 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 })
}
}
})
.collect()
}
}
+8 -5
View File
@@ -4,7 +4,7 @@
use iced::Subscription;
use common::{Service, ServicePayloadKind};
use common::{Service, ServiceEvent};
/// Conversion from a route key to the subscription backing it.
///
@@ -12,13 +12,16 @@ use common::{Service, ServicePayloadKind};
/// both `Service` and `Subscription` are foreign to this crate — the orphan
/// rule blocks a foreign trait (`From`) on a foreign type (`Subscription`).
pub trait IntoSubscription {
fn into_subscription(self) -> Subscription<ServicePayloadKind>;
fn into_subscription(self) -> Subscription<ServiceEvent>;
}
impl IntoSubscription for Service {
fn into_subscription(self) -> Subscription<ServicePayloadKind> {
match self {
Service::Clock(_) => service_datetime::ClockTicker::run(),
fn into_subscription(self) -> Subscription<ServiceEvent> {
match self.0 {
key if key.starts_with(service_datetime::NAMESPACE) => {
service_datetime::ClockTicker::run()
}
_ => Subscription::none(),
}
}
}