use std::time::Duration;
use tokio::sync::oneshot;
use orlando_core::{Envelope, GrainContext, GrainHandler, Message};
#[derive(Debug)]
pub struct TimerTick {
pub name: String,
}
impl Message for TimerTick {
type Result = ();
}
#[derive(Debug)]
pub struct TimerHandle {
_cancel: oneshot::Sender<()>,
}
pub fn register_timer<G>(
ctx: &GrainContext,
name: impl Into<String>,
period: Duration,
) -> TimerHandle
where
G: GrainHandler<TimerTick>,
{
let name = name.into();
let sender = ctx
.self_sender()
.expect("grain must be active to register a timer");
let (cancel_tx, mut cancel_rx) = oneshot::channel::<()>();
tokio::spawn(async move {
let mut interval = tokio::time::interval(period);
interval.tick().await;
loop {
tokio::select! {
_ = interval.tick() => {
let tick_name = name.clone();
let envelope = Envelope::new(Box::new(move |state_any, ctx| {
let Some(state) = state_any.downcast_mut::<G::State>() else {
tracing::error!("grain state type mismatch in timer tick — dropped");
return Box::pin(async {});
};
let tick = TimerTick { name: tick_name };
Box::pin(async move {
<G as GrainHandler<TimerTick>>::handle(state, tick, ctx).await;
})
}));
if sender.send(envelope).await.is_err() {
tracing::debug!("timer {:?}: grain mailbox closed, stopping", name);
break;
}
}
_ = &mut cancel_rx => {
tracing::debug!("timer {:?}: cancelled", name);
break;
}
}
}
});
TimerHandle {
_cancel: cancel_tx,
}
}