Scheduled reducers allow you to execute logic at a specific time or periodically (e.g., for game loops). Scheduling is transactional: if a reducer that schedules another reducer fails, the scheduled call will not be created.
To implement a scheduled reducer:
- Define a table with the
scheduled(reducer_function_name) parameter in the #[table] attribute. - The table must contain a primary key (typically
#[auto_inc]) and a field of type spacetimedb::ScheduleAt. - Define the reducer function with the
#[reducer] attribute. Its second argument must be the scheduling table struct.
Scheduling Types:
- One-off: Create
ScheduleAt from a spacetimedb::Timestamp. - Periodic: Create
ScheduleAt from a spacetimedb::TimeDuration or std::time::Duration.
Security Note: Scheduled reducers are still public. To prevent clients from calling them directly, verify that ctx.sender() == ctx.identity().
use spacetimedb::{table, reducer, ReducerContext, Timestamp, TimeDuration, ScheduleAt, Table};
use log::info;
#[table(accessor = send_message_schedule, scheduled(send_message))]
struct SendMessageSchedule {
#[primary_key]
#[auto_inc]
scheduled_id: u64,
scheduled_at: ScheduleAt,
text: String,
}
#[reducer]
fn send_message(ctx: &ReducerContext, args: SendMessageSchedule) -> Result<(), String> {
// Security check: ensure only the scheduler (the module itself) can call this
if ctx.sender() != ctx.identity() {
return Err("Reducer `send_message` may not be invoked by clients, only via scheduling.".into());
}
info!("Scheduled SendMessage: {}", args.text);
Ok(())
}
#[reducer(init)]
fn init(ctx: &ReducerContext) -> Result<(), String> {
let ten_seconds = TimeDuration::from_micros(10_000_000);
// Schedule one-off
let future_timestamp: Timestamp = ctx.timestamp + ten_seconds;
ctx.db.send_message_schedule().insert(SendMessageSchedule {
scheduled_id: 0,
text: "One-time message".to_string(),
scheduled_at: future_timestamp.into()
});
// Schedule periodic (every 10 seconds)
ctx.db.send_message_schedule().insert(SendMessageSchedule {
scheduled_id: 0,
text: "Periodic message".to_string(),
scheduled_at: ten_seconds.into()
});
Ok(())
}