| Crates.io | obsidian-scheduler |
| lib.rs | obsidian-scheduler |
| version | 0.1.0 |
| created_at | 2025-10-02 18:13:06.029528+00 |
| updated_at | 2025-10-02 18:13:06.029528+00 |
| description | A Rust library for scheduling tasks with both callback and event-based timers. |
| homepage | |
| repository | https://github.com/Obsidian-Minecraft-Server-Portal/scheduler.git |
| max_upload_size | |
| id | 1864779 |
| size | 67,378 |
A flexible, async-first timer library for Rust built on top of Tokio. Obsidian Scheduler provides two different timer paradigms to suit different use cases: Callback Timers and Event Timers.
Add this to your Cargo.toml:
[dependencies]
obsidian-scheduler = "0.1.0"
By default, only callback-timers is enabled. You can customize which features you need:
[dependencies]
obsidian-scheduler = { version = "0.1.0", features = ["event-timers"] }
Available features:
callback-timers (default): Enable CallbackTimer functionalityevent-timers: Enable EventTimer functionality with broadcast supportlog: Enable logging support for error messagesserde: Enable serialization support for timer structuresexamples: Enable all features needed to run examplesNote: At least one of callback-timers or event-timers must be enabled.
The CallbackTimer executes an async closure at regular intervals. The callback receives a TimerHandle that can be used to control the timer from within the callback itself.
use obsidian_scheduler::callback::CallbackTimer;
use obsidian_scheduler::timer_trait::Timer;
use std::time::Duration;
#[tokio::main]
async fn main() {
let timer = CallbackTimer::new(
async |timer_handle| {
println!("Timer fired!");
timer_handle.stop(); // Stop the timer after the first fire
Ok(())
},
Duration::from_secs(5),
);
timer.start();
println!("Waiting for timer to fire...");
while timer.is_running().await {
tokio::time::sleep(Duration::from_secs(1)).await;
}
println!("Timer stopped!");
}
use obsidian_scheduler::callback::CallbackTimer;
use obsidian_scheduler::timer_trait::Timer;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
let counter = Arc::new(Mutex::new(0));
let counter_clone = Arc::clone(&counter);
let timer = CallbackTimer::new(
move |timer_handle| {
let counter = counter_clone.clone();
async move {
let mut count = counter.lock().await;
*count += 1;
println!("Timer fired! Count: {}", *count);
if *count >= 3 {
println!("Stopping timer after 3 fires");
timer_handle.stop();
}
Ok(())
}
},
Duration::from_secs(2),
);
timer.start();
while timer.is_running().await {
tokio::time::sleep(Duration::from_secs(1)).await;
}
}
The EventTimer uses a broadcast channel pattern, allowing multiple subscribers to receive timer events. This is ideal for pub-sub scenarios where multiple components need to react to the same timer.
use obsidian_scheduler::event::EventTimer;
use obsidian_scheduler::timer_trait::Timer;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let timer = EventTimer::new("my_timer", tokio::time::Duration::from_secs(5))?;
// Subscribe to receive timer events
let mut receiver = timer.subscribe();
// Start the timer
timer.start().await?;
println!("Timer started, waiting for events...");
// Wait for the first event
match receiver.recv().await {
Ok(event_name) => {
println!("Timer event received: {}", event_name);
}
Err(e) => {
eprintln!("Error receiving event: {}", e);
}
}
// Stop the timer
timer.stop().await?;
println!("Timer stopped.");
Ok(())
}
use obsidian_scheduler::event::EventTimer;
use obsidian_scheduler::timer_trait::Timer;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let timer = EventTimer::new("my_timer", tokio::time::Duration::from_secs(3))?;
// Subscribe multiple receivers to the same timer (broadcast pattern)
let mut receiver1 = timer.subscribe();
let mut receiver2 = timer.subscribe();
let mut receiver3 = timer.subscribe();
timer.start().await?;
println!("Timer started with 3 subscribers...");
// Spawn tasks for each receiver
let task1 = tokio::spawn(async move {
for i in 1..=3 {
match receiver1.recv().await {
Ok(event_name) => {
println!("Receiver 1 got event #{}: {}", i, event_name);
}
Err(e) => {
eprintln!("Receiver 1 error: {}", e);
break;
}
}
}
});
let task2 = tokio::spawn(async move {
for i in 1..=3 {
match receiver2.recv().await {
Ok(event_name) => {
println!("Receiver 2 got event #{}: {}", i, event_name);
}
Err(e) => {
eprintln!("Receiver 2 error: {}", e);
break;
}
}
}
});
let task3 = tokio::spawn(async move {
for i in 1..=3 {
match receiver3.recv().await {
Ok(event_name) => {
println!("Receiver 3 got event #{}: {}", i, event_name);
}
Err(e) => {
eprintln!("Receiver 3 error: {}", e);
break;
}
}
}
});
// Wait for all receivers to complete
let _ = tokio::join!(task1, task2, task3);
timer.stop().await?;
println!("Timer stopped. All receivers completed.");
// Clean up the timer
timer.drop().await;
Ok(())
}
The EventTimer supports a global registry that allows you to retrieve timers by name from different threads or tasks. This is useful for scenarios where you need to access and control a timer from a different part of your application.
use obsidian_scheduler::event::EventTimer;
use obsidian_scheduler::timer_trait::Timer;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Create and start a timer in the main thread
let timer = EventTimer::new("cross_thread_timer", tokio::time::Duration::from_secs(2))?;
// Subscribe in the main thread
let mut main_receiver = timer.subscribe();
// Start the timer
timer.start().await?;
println!("Timer started in main thread");
// Spawn a separate task that retrieves the timer by name
let thread_task = tokio::spawn(async move {
// Retrieve the timer by name from a different task/thread
if let Some(retrieved_timer) = EventTimer::get_timer_by_name("cross_thread_timer").await {
println!("Successfully retrieved timer from different task");
// Subscribe to the timer from this thread
let mut thread_receiver = retrieved_timer.subscribe();
// Receive events
for i in 1..=3 {
match thread_receiver.recv().await {
Ok(event_name) => {
println!("Task received event #{}: {}", i, event_name);
}
Err(e) => {
eprintln!("Error receiving event: {}", e);
break;
}
}
}
// Stop the timer from this thread
retrieved_timer.stop().await?;
println!("Timer stopped from different task");
}
Ok::<(), anyhow::Error>(())
});
// Main thread also receives events
for i in 1..=3 {
match main_receiver.recv().await {
Ok(event_name) => {
println!("Main thread received event #{}: {}", i, event_name);
}
Err(_) => break,
}
}
thread_task.await??;
Ok(())
}
This example demonstrates:
Both CallbackTimer and EventTimer implement the Timer trait:
pub trait Timer {
/// Starts the timer, the timer will run continuously until stopped
async fn start(&self) -> Result<(), SchedulerError>;
/// Aborts the timer early
async fn stop(&self) -> Result<(), SchedulerError>;
/// Resets the elapsed time to zero without stopping the timer
async fn reset(&self) -> Result<(), SchedulerError>;
/// Returns true if the timer is currently running
async fn is_running(&self) -> bool;
}
impl CallbackTimer {
/// Creates a new CallbackTimer with the given async callback and interval
pub fn new<F, Fut>(callback: F, interval: Duration) -> Arc<Self>
where
F: Fn(TimerHandle) -> Fut + Send + Sync + 'static,
Fut: Future<Output = Result<()>> + Send + 'static;
}
TimerHandle methods:
stop(): Stops the timer from within the callbackimpl EventTimer {
/// Creates a new EventTimer with the given name and interval
pub fn new(
event_name: impl Into<String>,
interval: tokio::time::Duration,
) -> Result<Self, SchedulerError>;
/// Subscribe to receive timer events
pub fn subscribe(&self) -> broadcast::Receiver<String>;
/// Get a timer by name from the global registry
pub fn get_timer_by_name(name: impl AsRef<str>) -> Option<EventTimer>;
/// Unregisters the timer from the global registry
/// This should be called after stopping the timer to prevent memory leaks
pub async fn drop(&self);
}
The library provides a comprehensive SchedulerError enum:
pub enum SchedulerError {
TimerAlreadyExists(String),
TimerNotFound(String),
TimerNotRunning(String),
TimerStartError(String),
TimerStopError(String),
BroadcastError(RecvError),
JoinHandleError(String),
}
The project includes several examples demonstrating different use cases:
# Basic callback timer
cargo run --example callback_timer_basic --features examples
# Callback timer with state management
cargo run --example callback_timer --features examples
# Basic event timer
cargo run --example event_timer --features examples
# Event timer with multiple subscribers
cargo run --example event_timer_multiple --features examples
# Event timer with cross-thread access
cargo run --example event_timer_cross_thread --features examples
time, rt, and sync featurestokio/macros featuretokio: Async runtime and utilitiesanyhow: Error handlingthiserror: Custom error typeslog (optional): Logging supportserde (optional): Serialization supportCallbackTimer: Best for self-contained timer logic where the timer needs to execute specific code at intervals. The callback has direct control over the timer and can carry state.
EventTimer: Best for scenarios where multiple components need to react to the same timer. Uses a broadcast pattern allowing any number of subscribers to receive events.
The library uses Rust's stabilized async closure features instead of Pin<Box<dyn Future>> for better ergonomics and performance. This allows natural async closure syntax while maintaining zero-cost abstractions.
Both timer types use Arc<Mutex<T>> for interior mutability, ensuring thread-safe access across async tasks. The CallbackTimer returns Arc<Self> from new() to make cloning and sharing explicit and efficient.
(Add your license information here)
(Add contribution guidelines here)