msg type -> wire::push() -> update handle wire into module/service/ect
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
use iced::{window, Rectangle};
|
||||
|
||||
use crate::Wire;
|
||||
|
||||
/// What a module asks the app to do — the single effect vocabulary shared by
|
||||
/// modules and app, so mapping module output into a `Message` is a plain
|
||||
/// `.map(Message::Effect)` with no mirror enum.
|
||||
@@ -12,4 +14,6 @@ pub enum ModuleEffect {
|
||||
BoundsFound(String, Rectangle),
|
||||
/// Request removal of the popup surface.
|
||||
ClosePopup(window::Id),
|
||||
/// Route a wire to its target (a module, a service inbox, or a topic).
|
||||
Send(Wire),
|
||||
}
|
||||
|
||||
@@ -6,10 +6,14 @@
|
||||
|
||||
mod config;
|
||||
mod effect;
|
||||
mod messages;
|
||||
mod module;
|
||||
mod service;
|
||||
mod wire;
|
||||
|
||||
pub use config::BarbarConfig;
|
||||
pub use effect::ModuleEffect;
|
||||
pub use module::{BarModule, ModuleMsg};
|
||||
pub use service::{Service, ServiceEvent};
|
||||
pub use messages::{ClockKind, ClockMsg, ClockPayload};
|
||||
pub use module::BarModule;
|
||||
pub use service::Service;
|
||||
pub use wire::{Endpoint, Inbox, Namespace, Target, Wire};
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
//! Wire vocabulary: every message type that crosses a crate boundary.
|
||||
//!
|
||||
//! Laid out to mirror the crate that owns the behavior: `modules/*` are
|
||||
//! messages a bar module sends or receives, `services/*` are messages a
|
||||
//! service produces or accepts. Types live here so either side can name a
|
||||
//! payload without depending on the other's crate; only the types are
|
||||
//! shared, the behavior stays in the owning crate.
|
||||
|
||||
pub mod modules;
|
||||
pub mod services;
|
||||
|
||||
pub use modules::*;
|
||||
pub use services::*;
|
||||
@@ -0,0 +1,10 @@
|
||||
//! Clock module messages.
|
||||
|
||||
/// A module-local message for the clock (bar/popup interactions).
|
||||
#[derive(Clone)]
|
||||
pub enum ClockMsg {
|
||||
/// Toggle the seconds suffix.
|
||||
ToggleSeconds,
|
||||
/// Open the big-time popup.
|
||||
OpenPopup,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Messages owned by bar modules.
|
||||
|
||||
mod clock;
|
||||
|
||||
pub use clock::ClockMsg;
|
||||
@@ -0,0 +1,27 @@
|
||||
//! Clock service messages.
|
||||
|
||||
use crate::Service;
|
||||
|
||||
/// 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,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
//! Messages owned by services.
|
||||
|
||||
mod datetime;
|
||||
|
||||
pub use datetime::{ClockKind, ClockPayload};
|
||||
@@ -1,56 +1,22 @@
|
||||
use std::any::Any;
|
||||
use std::error::Error;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use iced::window;
|
||||
use iced::{Element, Task};
|
||||
|
||||
use crate::ModuleEffect;
|
||||
|
||||
/// A module-local message, boxed with its owner's `id`. The app routes by
|
||||
/// `id` and never names a module's message type; the module downcasts.
|
||||
#[derive(Clone)]
|
||||
pub struct ModuleMsg {
|
||||
pub id: &'static str,
|
||||
pub payload: Arc<dyn Any + Send + Sync>,
|
||||
}
|
||||
|
||||
impl ModuleMsg {
|
||||
/// Wraps a module's own message; `id` must match its `BarModule::id`.
|
||||
pub fn new<T: Any + Send + Sync>(id: &'static str, msg: T) -> Self {
|
||||
Self {
|
||||
id,
|
||||
payload: Arc::new(msg),
|
||||
}
|
||||
}
|
||||
|
||||
/// Downcasts to the module's own message type.
|
||||
pub fn downcast<T: Any + Send + Sync>(&self) -> Option<&T> {
|
||||
self.payload.downcast_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for ModuleMsg {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("ModuleMsg")
|
||||
.field("id", &self.id)
|
||||
.field("payload", &self.payload.type_id())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
use crate::{ModuleEffect, Wire};
|
||||
|
||||
/// A bar module. Object-safe; modules live in a `BTreeMap` keyed by id. The
|
||||
/// `ModuleMsg` boundary keeps the app ignorant of each module's message enum.
|
||||
/// `Wire` boundary keeps the app ignorant of each module's message enum —
|
||||
/// the module downcasts whatever arrives.
|
||||
pub trait BarModule: Send {
|
||||
fn id(&self) -> &'static str;
|
||||
|
||||
/// Bar contents. When `window_id` is `Some`, this is the module's popup
|
||||
/// surface and the module returns its popup contents instead.
|
||||
fn view(&self, window_id: Option<window::Id>) -> Element<'_, ModuleMsg>;
|
||||
fn view(&self, window_id: Option<window::Id>) -> Element<'_, Wire>;
|
||||
|
||||
/// Handles a routed message; may return app-level tasks (e.g. popups).
|
||||
fn update(&mut self, msg: ModuleMsg) -> Task<ModuleEffect>;
|
||||
fn update(&mut self, msg: Wire) -> Task<ModuleEffect>;
|
||||
|
||||
/// What services this module wants.
|
||||
fn services(&self) -> Vec<crate::Service>;
|
||||
|
||||
@@ -1,16 +1,5 @@
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Opaque route key identifying a service subscription. Services name their
|
||||
/// own keys, so `common` never enumerates them and adding a service never
|
||||
/// edits this crate.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct Service(pub &'static str);
|
||||
|
||||
/// A service event: the route key plus an erased typed payload. The app reads
|
||||
/// `key` for fan-out; the consuming module downcasts `payload`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ServiceEvent {
|
||||
pub key: Service,
|
||||
pub payload: Arc<dyn Any + Send + Sync>,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
use std::any::Any;
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use iced::futures::channel::mpsc;
|
||||
|
||||
/// Which namespace an endpoint lives in. Module ids and service route keys
|
||||
/// are separate namespaces, so a module and a service may share a name.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum Namespace {
|
||||
Module,
|
||||
Service,
|
||||
}
|
||||
|
||||
/// A wire's sender: a module id or a service route key.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct Endpoint {
|
||||
pub ns: Namespace,
|
||||
pub name: &'static str,
|
||||
}
|
||||
|
||||
impl Endpoint {
|
||||
pub const fn module(name: &'static str) -> Self {
|
||||
Self {
|
||||
ns: Namespace::Module,
|
||||
name,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn service(name: &'static str) -> Self {
|
||||
Self {
|
||||
ns: Namespace::Service,
|
||||
name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a wire goes. Core owns the routing rule for each variant.
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
|
||||
pub enum Target {
|
||||
/// One module, by id.
|
||||
Module(&'static str),
|
||||
/// One running service's inbox, by route key.
|
||||
Service(&'static str),
|
||||
/// Every module subscribed to a route key (a service's fan-out).
|
||||
Topic(&'static str),
|
||||
}
|
||||
|
||||
/// One typed message on the wire. Core routes by `to`; the receiver
|
||||
/// downcasts `payload` to one of *its own* types. Sender and receiver keep
|
||||
/// their own concrete types — nothing here enumerates them.
|
||||
#[derive(Clone)]
|
||||
pub struct Wire {
|
||||
pub from: Endpoint,
|
||||
pub to: Target,
|
||||
pub payload: Arc<dyn Any + Send + Sync>,
|
||||
}
|
||||
|
||||
impl Wire {
|
||||
/// A module-local message: addressed straight back to one module.
|
||||
pub fn module<T: Any + Send + Sync>(from: Endpoint, id: &'static str, msg: T) -> Self {
|
||||
Self {
|
||||
from,
|
||||
to: Target::Module(id),
|
||||
payload: Arc::new(msg),
|
||||
}
|
||||
}
|
||||
|
||||
/// Sent into a service's inbox.
|
||||
pub fn service<T: Any + Send + Sync>(from: Endpoint, key: &'static str, msg: T) -> Self {
|
||||
Self {
|
||||
from,
|
||||
to: Target::Service(key),
|
||||
payload: Arc::new(msg),
|
||||
}
|
||||
}
|
||||
|
||||
/// Published by a service to every module subscribed to `key`.
|
||||
pub fn topic<T: Any + Send + Sync>(from: Endpoint, key: &'static str, msg: T) -> Self {
|
||||
Self {
|
||||
from,
|
||||
to: Target::Topic(key),
|
||||
payload: Arc::new(msg),
|
||||
}
|
||||
}
|
||||
|
||||
/// Downcasts to the receiver's own message type.
|
||||
pub fn downcast<T: Any + Send + Sync>(&self) -> Option<&T> {
|
||||
self.payload.downcast_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Wire {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Wire")
|
||||
.field("from", &self.from)
|
||||
.field("to", &self.to)
|
||||
.field("payload", &self.payload.type_id())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
/// A service's inbound mailbox: modules send `Wire`s here, the service's
|
||||
/// subscription task reads them. Cloneable; the receiver is taken once.
|
||||
///
|
||||
/// The `Arc<Mutex<Option<..>>>` lets `Bar` keep a clone and hand the same
|
||||
/// value to `Subscription::run_with` on every rebuild without moving the
|
||||
/// single-consumer receiver out of its reach.
|
||||
#[derive(Clone)]
|
||||
pub struct Inbox {
|
||||
key: &'static str,
|
||||
tx: mpsc::UnboundedSender<Wire>,
|
||||
rx: Arc<Mutex<Option<mpsc::UnboundedReceiver<Wire>>>>,
|
||||
}
|
||||
|
||||
impl Inbox {
|
||||
/// Creates the channel backing a service's inbox.
|
||||
pub fn new(key: &'static str) -> Self {
|
||||
let (tx, rx) = mpsc::unbounded();
|
||||
Self {
|
||||
key,
|
||||
tx,
|
||||
rx: Arc::new(Mutex::new(Some(rx))),
|
||||
}
|
||||
}
|
||||
|
||||
/// The service's route key, used as the `Topic` it publishes under.
|
||||
pub fn key(&self) -> &'static str {
|
||||
self.key
|
||||
}
|
||||
|
||||
/// Sends a wire into the service; `false` if the service is gone.
|
||||
pub fn send(&self, wire: Wire) -> bool {
|
||||
self.tx.unbounded_send(wire).is_ok()
|
||||
}
|
||||
|
||||
/// Takes the receiver; the service stream calls this once, when iced
|
||||
/// first starts it.
|
||||
pub fn take(&self) -> Option<mpsc::UnboundedReceiver<Wire>> {
|
||||
self.rx.lock().unwrap().take()
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Inbox {
|
||||
/// Identity for `Subscription::run_with`: the route key, so returning
|
||||
/// the same service subscription each pass does not restart its stream.
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.key.hash(state);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user