use gtk::glib;
use tracing::info_span;
use crate::{
Component, ComponentBuilder, ComponentParts, ComponentSender, GuardedReceiver, Receiver,
RuntimeSenders, Sender, ShutdownOnDrop, SimpleComponent,
};
use std::fmt::Debug;
use std::{any, thread};
pub trait Worker: Sized + Send + 'static {
type Init: 'static + Send;
type Input: 'static + Send + Debug;
type Output: 'static + Send + Debug;
fn init(init: Self::Init, sender: ComponentSender<Self>) -> Self;
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>);
}
impl<T> SimpleComponent for T
where
T: Worker + 'static,
{
type Root = ();
type Widgets = ();
type Init = <Self as Worker>::Init;
type Input = <Self as Worker>::Input;
type Output = <Self as Worker>::Output;
fn init_root() -> Self::Root {}
fn init(
init: Self::Init,
_root: Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let model = Self::init(init, sender);
ComponentParts { model, widgets: () }
}
fn update(&mut self, message: Self::Input, sender: ComponentSender<Self>) {
Self::update(self, message, sender);
}
}
impl<C> ComponentBuilder<C>
where
C: Component<Root = (), Widgets = ()> + Send,
C::Input: Send,
C::Output: Send,
C::CommandOutput: Send,
{
pub fn detach_worker(self, payload: C::Init) -> WorkerHandle<C> {
let Self { root, .. } = self;
let (input_sender, input_receiver) = crate::channel::<C::Input>();
let RuntimeSenders {
output_sender,
output_receiver,
cmd_sender,
cmd_receiver,
shutdown_notifier,
shutdown_recipient,
shutdown_on_drop,
mut shutdown_event,
} = RuntimeSenders::<C::Output, C::CommandOutput>::new();
let component_sender = ComponentSender::new(
input_sender.clone(),
output_sender.clone(),
cmd_sender,
shutdown_recipient,
);
let mut state = C::init(payload, root, component_sender.clone());
thread::spawn(move || {
let context = glib::MainContext::thread_default().unwrap_or_default();
context.block_on(async move {
let mut cmd = GuardedReceiver::new(cmd_receiver);
let mut input = GuardedReceiver::new(input_receiver);
loop {
futures::select!(
message = input => {
let ComponentParts {
model,
widgets,
} = &mut state;
let span = info_span!(
"update_with_view",
input=?message,
component=any::type_name::<C>(),
id=model.id(),
);
let _enter = span.enter();
model.update_with_view(widgets, message, component_sender.clone(), &root);
}
message = cmd => {
let ComponentParts {
model,
widgets,
} = &mut state;
let span = info_span!(
"update_cmd_with_view",
cmd_output=?message,
component=any::type_name::<C>(),
id=model.id(),
);
let _enter = span.enter();
model.update_cmd_with_view(widgets, message, component_sender.clone(), &root);
},
_ = shutdown_event => {
let ComponentParts {
model,
widgets,
} = &mut state;
model.shutdown(widgets, output_sender);
shutdown_notifier.shutdown();
return;
}
);
}
});
});
WorkerHandle {
sender: input_sender,
receiver: output_receiver,
shutdown_on_drop,
}
}
}
#[derive(Debug)]
pub struct WorkerHandle<W: Component> {
sender: Sender<W::Input>,
receiver: Receiver<W::Output>,
shutdown_on_drop: ShutdownOnDrop,
}
impl<W: Component> WorkerHandle<W>
where
W::Input: 'static,
W::Output: 'static,
{
pub fn connect_receiver<F: FnMut(&mut Sender<W::Input>, W::Output) + 'static>(
self,
mut func: F,
) -> WorkerController<W> {
let Self {
sender,
receiver,
shutdown_on_drop,
} = self;
let mut sender_ = sender.clone();
crate::spawn_local(async move {
while let Some(event) = receiver.recv().await {
func(&mut sender_, event);
}
});
WorkerController {
sender,
shutdown_on_drop,
}
}
pub fn forward<X: 'static, F: (Fn(W::Output) -> X) + 'static>(
self,
sender: &Sender<X>,
transform: F,
) -> WorkerController<W> {
let Self {
sender: own_sender,
receiver,
shutdown_on_drop,
} = self;
crate::spawn_local(receiver.forward(sender.clone(), transform));
WorkerController {
sender: own_sender,
shutdown_on_drop,
}
}
#[must_use]
pub fn detach(self) -> WorkerController<W> {
let Self {
sender,
shutdown_on_drop,
..
} = self;
WorkerController {
sender,
shutdown_on_drop,
}
}
}
#[derive(Debug)]
pub struct WorkerController<W: Component> {
sender: Sender<W::Input>,
shutdown_on_drop: ShutdownOnDrop,
}
impl<W: Component> WorkerController<W> {
pub fn emit(&self, event: W::Input) {
self.sender.send(event).unwrap();
}
#[must_use]
pub const fn sender(&self) -> &Sender<W::Input> {
&self.sender
}
pub fn detach_runtime(&mut self) {
self.shutdown_on_drop.deactivate();
}
}