use std::any::{Any, TypeId};
use std::future::Future;
use std::marker::PhantomData;
use std::sync::Arc;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
use tokio::task::JoinHandle;
use xilem_core::Resource;
use crate::ViewCtx;
use crate::core::anymore::AnyDebug;
use crate::core::{
MessageContext, MessageProxy, MessageResult, Mut, NoElement, View, ViewId, ViewMarker,
ViewPathTracker,
};
pub fn worker<F, H, M, S, V, State, Action, Fut>(
init_future: F,
store_sender: S,
on_response: H,
) -> Worker<F, H, M, impl Fn(&mut State, &mut Dummy, UnboundedSender<V>) + 'static, V, Dummy>
where
F: Fn(MessageProxy<M>, UnboundedReceiver<V>) -> Fut,
Fut: Future<Output = ()> + Send + 'static,
S: Fn(&mut State, UnboundedSender<V>) + 'static,
H: Fn(&mut State, M) -> Action + 'static,
M: AnyDebug + Send + 'static,
{
const {
assert!(
size_of::<F>() == 0,
"`worker` will not be ran again when its captured variables are updated.\n\
To ignore this warning, use `worker_raw`."
);
};
Worker {
init_future,
store_sender: move |state: &mut State, _dummy: &mut Dummy, sender: UnboundedSender<V>| {
store_sender(state, sender);
},
on_response,
message: PhantomData,
}
}
pub fn env_worker<F, H, M, S, V, Res, State, Action, Fut>(
init_future: F,
store_sender: S,
on_response: H,
) -> Worker<F, H, M, S, V, Res>
where
F: Fn(MessageProxy<M>, UnboundedReceiver<V>) -> Fut,
Fut: Future<Output = ()> + Send + 'static,
S: Fn(&mut State, &mut Res, UnboundedSender<V>),
H: Fn(&mut State, M) -> Action + 'static,
M: AnyDebug + Send + 'static,
Res: Resource,
{
const {
assert!(
size_of::<F>() == 0,
"`worker` will not be ran again when its captured variables are updated.\n\
To ignore this warning, use `worker_raw`."
);
};
Worker {
init_future,
store_sender,
on_response,
message: PhantomData,
}
}
#[derive(Debug)]
#[doc(hidden)]
pub struct Dummy;
impl Resource for Dummy {}
pub fn worker_raw<M, V, S, F, H, State, Action, Fut>(
init_future: F,
store_sender: S,
on_response: H,
) -> Worker<F, H, M, impl Fn(&mut State, &mut Dummy, UnboundedSender<V>) + 'static, V, Dummy>
where
F: Fn(MessageProxy<M>, UnboundedReceiver<V>) -> Fut,
Fut: Future<Output = ()> + Send + 'static,
S: Fn(&mut State, UnboundedSender<V>) + 'static,
H: Fn(&mut State, M) -> Action + 'static,
M: AnyDebug + Send + 'static,
{
Worker {
init_future,
on_response,
store_sender: move |state: &mut State, _dummy: &mut Dummy, sender: UnboundedSender<V>| {
store_sender(state, sender);
},
message: PhantomData,
}
}
pub struct Worker<F, H, M, S, V, Res> {
init_future: F,
store_sender: S,
on_response: H,
message: PhantomData<fn(M, V, Res)>,
}
impl<F, H, M, S, V, Res> ViewMarker for Worker<F, H, M, S, V, Res> {}
impl<State, Action, F, H, M, Fut, S, V, Res> View<State, Action, ViewCtx>
for Worker<F, H, M, S, V, Res>
where
Res: Resource,
F: Fn(MessageProxy<M>, UnboundedReceiver<V>) -> Fut + 'static,
V: Send + 'static,
Fut: Future<Output = ()> + Send + 'static,
S: Fn(&mut State, &mut Res, UnboundedSender<V>) + 'static,
H: Fn(&mut State, M) -> Action + 'static,
M: AnyDebug + Send + 'static,
{
type Element = NoElement;
type ViewState = JoinHandle<()>;
fn build(&self, ctx: &mut ViewCtx, app_state: &mut State) -> (Self::Element, Self::ViewState) {
let path: Arc<[ViewId]> = ctx.view_path().into();
let proxy = ctx.proxy();
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
let env = ctx.environment();
if TypeId::of::<Res>() != TypeId::of::<Dummy>() {
let pos = env.get_slot_for_type::<Res>();
let Some(pos) = pos else {
panic!(
"Xilem: Tried to get context for {}, but it hasn't been provided. Did you forget to wrap this view with `xilem_core::environment::provides`?",
core::any::type_name::<Res>()
);
};
let slot = &mut env.slots[usize::try_from(pos).unwrap()];
let Some(value) = slot.item.as_mut() else {
panic!(
"Xilem: Tried to get context for {}, but it hasn't been `Provided`.",
core::any::type_name::<Res>()
);
};
(self.store_sender)(
app_state,
value
.value
.downcast_mut::<Res>()
.expect("Environment's slots should have the correct types."),
tx,
);
} else {
let value: &mut dyn Any = &mut Dummy;
let res = value.downcast_mut::<Res>().expect("Has same type id.");
(self.store_sender)(app_state, res, tx);
}
let handle = ctx
.runtime()
.spawn((self.init_future)(MessageProxy::new(proxy, path), rx));
(NoElement, handle)
}
fn rebuild(
&self,
_prev: &Self,
_view_state: &mut Self::ViewState,
_: &mut ViewCtx,
(): Mut<'_, Self::Element>,
_: &mut State,
) {
}
fn teardown(&self, handle: &mut Self::ViewState, _: &mut ViewCtx, _: Mut<'_, Self::Element>) {
handle.abort();
}
fn message(
&self,
_: &mut Self::ViewState,
message: &mut MessageContext,
_element: Mut<'_, Self::Element>,
app_state: &mut State,
) -> MessageResult<Action> {
debug_assert!(
message.remaining_path().is_empty(),
"id path should be empty in Task::message"
);
let message = message.take_message::<M>().unwrap();
MessageResult::Action((self.on_response)(app_state, *message))
}
}