changes
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
# services
|
||||
|
||||
Background services. A service owns a route key, runs as an iced
|
||||
`Subscription`, receives `common::Wire`s on its `Inbox`, and publishes
|
||||
payloads back to every module subscribed to its key. Core maps a `Service`
|
||||
route key to a subscription through `IntoSubscription`.
|
||||
|
||||
Message types are shared, so they live in the `common` crate, under
|
||||
`common/src/messages/services/`. That is what lets a service and the modules
|
||||
that consume it name the same payload without either depending on the other.
|
||||
|
||||
## Getting started: add a service
|
||||
|
||||
1. Declare the service's route key and payload in
|
||||
`crates/common/src/messages/services/greeter.rs`:
|
||||
|
||||
```rust
|
||||
//! Greeter service messages.
|
||||
|
||||
use crate::Service;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum GreeterKind {
|
||||
Hello,
|
||||
}
|
||||
|
||||
impl GreeterKind {
|
||||
/// Route key a module subscribes under to receive this kind's ticks.
|
||||
pub fn key(self) -> Service {
|
||||
Service(match self {
|
||||
Self::Hello => "greeter.hello",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct GreeterPayload {
|
||||
pub value: String,
|
||||
}
|
||||
```
|
||||
|
||||
2. Re-export them from `crates/common/src/messages/services/mod.rs`:
|
||||
|
||||
```rust
|
||||
mod datetime;
|
||||
mod greeter;
|
||||
mod weather;
|
||||
|
||||
pub use datetime::{ClockKind, ClockPayload};
|
||||
pub use greeter::{GreeterKind, GreeterPayload};
|
||||
pub use weather::*;
|
||||
```
|
||||
|
||||
3. Create `crates/services/src/greeter.rs`:
|
||||
|
||||
```rust
|
||||
use std::time::Duration;
|
||||
|
||||
use common::{Endpoint, Inbox, Wire, GreeterPayload};
|
||||
use iced::{futures::channel::mpsc, Subscription};
|
||||
|
||||
/// Route-key namespace owned by this service; keys look like `"greeter.hello"`.
|
||||
pub const NAMESPACE: &str = "greeter.";
|
||||
|
||||
pub struct GreeterService;
|
||||
|
||||
impl GreeterService {
|
||||
pub fn run(inbox: Inbox) -> Subscription<Wire> {
|
||||
Subscription::run_with(inbox, |inbox| {
|
||||
let key = inbox.key();
|
||||
let mut _rx = inbox.take().expect("inbox receiver is taken once");
|
||||
|
||||
iced::stream::channel(0, move |mut sender: mpsc::Sender<Wire>| async move {
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(5));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
// Fan-out: publish to every module subscribed to `key`.
|
||||
let wire = Wire::topic(Endpoint::service(key), key, GreeterPayload {
|
||||
value: "hello".into(),
|
||||
});
|
||||
if sender.send(wire).await.is_err() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
4. Add one arm to the `IntoSubscription` match in `crates/services/src/lib.rs`:
|
||||
|
||||
```rust
|
||||
pub mod greeter;
|
||||
|
||||
impl IntoSubscription for Service {
|
||||
fn into_subscription(self, inbox: Inbox) -> Subscription<Wire> {
|
||||
match self.0 {
|
||||
key if key.starts_with(datetime::NAMESPACE) => datetime::ClockTicker::run(inbox),
|
||||
key if key.starts_with(weather::NAMESPACE) => weather::WeatherService::run(inbox),
|
||||
key if key.starts_with(greeter::NAMESPACE) => greeter::GreeterService::run(inbox),
|
||||
_ => Subscription::none(),
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then a module subscribes by returning `GreeterKind::Hello.key()` from its
|
||||
`services()` and downcasts `GreeterPayload` in `update()`.
|
||||
|
||||
`run_with(inbox, ..)` keys the subscription by the inbox route key, so the
|
||||
same subscription returned on every rebuild is not restarted. The receiver is
|
||||
taken once inside the closure — don't call `take()` anywhere else.
|
||||
Reference in New Issue
Block a user