use std::future::Future;
use std::marker::PhantomData;
use std::rc::Rc;
use futures::FutureExt;
use futures::channel::oneshot;
use wasm_bindgen::UnwrapThrowExt;
use wasm_bindgen_futures::spawn_local;
use crate::ViewCtx;
use crate::context::MessageThunk;
use crate::core::anymore::AnyDebug;
use crate::core::{MessageContext, MessageResult, Mut, NoElement, View, ViewMarker};
pub fn task<M, F, H, State, Action, Fut>(init_future: F, on_event: H) -> Task<F, H, M>
where
F: Fn(TaskProxy, ShutdownSignal) -> Fut + 'static,
Fut: Future<Output = ()> + 'static,
H: Fn(&mut State, M) -> Action + 'static,
M: AnyDebug,
{
const {
assert!(
size_of::<F>() == 0,
"`task` will not be ran again when its captured variables are updated.\n\
To ignore this warning, use `task_raw`."
);
};
Task {
init_future,
on_event,
message: PhantomData,
}
}
pub fn task_raw<M, F, H, State, Action, Fut>(init_future: F, on_event: H) -> Task<F, H, M>
where
F: Fn(TaskProxy, ShutdownSignal) -> Fut + 'static,
Fut: Future<Output = ()> + 'static,
H: Fn(&mut State, M) -> Action + 'static,
{
Task {
init_future,
on_event,
message: PhantomData,
}
}
struct AbortHandle {
abort_tx: oneshot::Sender<()>,
}
impl AbortHandle {
fn abort(self) {
let _ = self.abort_tx.send(());
}
}
pub struct ShutdownSignal {
shutdown_rx: oneshot::Receiver<()>,
}
impl ShutdownSignal {
fn new() -> (Self, AbortHandle) {
let (abort_tx, shutdown_rx) = oneshot::channel();
(Self { shutdown_rx }, AbortHandle { abort_tx })
}
pub fn should_shutdown(&mut self) -> bool {
match self.shutdown_rx.try_recv() {
Ok(Some(())) | Err(oneshot::Canceled) => true,
Ok(None) => false,
}
}
pub fn into_future(self) -> impl Future<Output = ()> {
self.shutdown_rx.map(|_| ())
}
}
pub struct Task<F, H, M> {
init_future: F,
on_event: H,
message: PhantomData<fn() -> M>,
}
#[expect(
unnameable_types,
reason = "Implementation detail, public because of trait visibility rules"
)]
pub struct TaskState {
abort_handle: Option<AbortHandle>,
}
pub struct TaskProxy {
thunk: Rc<MessageThunk>,
}
impl TaskProxy {
pub fn send_message<M>(&self, message: M)
where
M: AnyDebug,
{
let thunk = Rc::clone(&self.thunk);
spawn_local(async move {
thunk.push_message(message);
});
}
}
impl<F, H, M> ViewMarker for Task<F, H, M> {}
impl<State, Action, F, H, M, Fut> View<State, Action, ViewCtx> for Task<F, H, M>
where
State: 'static,
Action: 'static,
F: Fn(TaskProxy, ShutdownSignal) -> Fut + 'static,
Fut: Future<Output = ()> + 'static,
H: Fn(&mut State, M) -> Action + 'static,
M: AnyDebug,
{
type Element = NoElement;
type ViewState = TaskState;
fn build(&self, ctx: &mut ViewCtx, _: &mut State) -> (Self::Element, Self::ViewState) {
let thunk = ctx.message_thunk();
let (shutdown_signal, abort_handle) = ShutdownSignal::new();
let view_state = TaskState {
abort_handle: Some(abort_handle),
};
let proxy = TaskProxy {
thunk: Rc::new(thunk),
};
spawn_local((self.init_future)(proxy, shutdown_signal));
(NoElement, view_state)
}
fn rebuild(
&self,
_: &Self,
_: &mut Self::ViewState,
_: &mut ViewCtx,
(): Mut<'_, Self::Element>,
_: &mut State,
) {
}
fn teardown(
&self,
view_state: &mut Self::ViewState,
_: &mut ViewCtx,
_: Mut<'_, Self::Element>,
) {
let handle = view_state.abort_handle.take().unwrap_throw();
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 AsyncRepeat::message, got {message:?}"
);
let message = message.take_message::<M>().unwrap();
MessageResult::Action((self.on_event)(app_state, *message))
}
}