use std::error::Error;
use std::fmt;
use std::future::Future;
use std::io;
use std::pin::Pin;
use calloop::channel::{Channel, Sender, channel};
use log::{debug, error};
use tokio::runtime::{Builder, Runtime};
use crate::widget::Msg;
pub type ProducerResult = Result<(), Box<dyn Error + Send + Sync>>;
pub type ProducerFuture = Pin<Box<dyn Future<Output = ProducerResult> + Send>>;
#[derive(Clone)]
pub struct MsgSender {
inner: Sender<Msg>,
}
impl MsgSender {
pub fn send(&self, msg: Msg) -> Result<(), Closed> {
self.inner.send(msg).map_err(|_| Closed)
}
}
impl fmt::Debug for MsgSender {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MsgSender").finish_non_exhaustive()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Closed;
impl fmt::Display for Closed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("render loop closed; message channel receiver was dropped")
}
}
impl Error for Closed {}
pub trait Producer: Send + 'static {
fn name(&self) -> String;
fn run(self: Box<Self>, tx: MsgSender) -> ProducerFuture;
}
pub fn from_fn<F, Fut>(name: impl Into<String>, f: F) -> Box<dyn Producer>
where
F: FnOnce(MsgSender) -> Fut + Send + 'static,
Fut: Future<Output = ProducerResult> + Send + 'static,
{
struct FnProducer<F> {
name: String,
f: F,
}
impl<F, Fut> Producer for FnProducer<F>
where
F: FnOnce(MsgSender) -> Fut + Send + 'static,
Fut: Future<Output = ProducerResult> + Send + 'static,
{
fn name(&self) -> String {
self.name.clone()
}
fn run(self: Box<Self>, tx: MsgSender) -> ProducerFuture {
Box::pin((self.f)(tx))
}
}
Box::new(FnProducer {
name: name.into(),
f,
})
}
pub struct ProducerBridge {
runtime: Runtime,
sender: Sender<Msg>,
}
impl ProducerBridge {
pub fn new() -> io::Result<(Self, Channel<Msg>)> {
let runtime = Builder::new_multi_thread()
.worker_threads(1)
.thread_name("tablero-producer")
.enable_all()
.build()?;
let (sender, channel) = channel();
Ok((Self { runtime, sender }, channel))
}
pub fn sender(&self) -> MsgSender {
MsgSender {
inner: self.sender.clone(),
}
}
pub fn spawn(&self, producer: Box<dyn Producer>) {
let tx = self.sender();
let name = producer.name();
self.runtime.spawn(async move {
match producer.run(tx).await {
Ok(()) => debug!("producer {name} finished"),
Err(e) => error!("producer {name} failed: {e}"),
}
});
}
pub fn spawn_task<F>(&self, name: impl Into<String>, future: F)
where
F: Future<Output = ProducerResult> + Send + 'static,
{
let name = name.into();
self.runtime.spawn(async move {
match future.await {
Ok(()) => debug!("task {name} finished"),
Err(e) => error!("task {name} failed: {e}"),
}
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn send_after_receiver_dropped_reports_closed() {
let (bridge, channel) = ProducerBridge::new().expect("runtime starts");
let tx = bridge.sender();
drop(channel); assert_eq!(tx.send(Msg::tick_now()), Err(Closed));
}
#[test]
fn from_fn_carries_its_name() {
let producer = from_fn("hyprland", |_tx| async { Ok(()) });
assert_eq!(producer.name(), "hyprland");
}
}