| Crates.io | bevy_cronjob |
| lib.rs | bevy_cronjob |
| version | 0.6.1 |
| created_at | 2023-08-02 06:15:11.495121+00 |
| updated_at | 2025-06-17 10:17:56.731718+00 |
| description | A simple helper to run cronjobs (at repeated schedule) in Bevy. |
| homepage | https://github.com/foxzool/bevy_cronjob |
| repository | https://github.com/foxzool/bevy_cronjob |
| max_upload_size | |
| id | 932439 |
| size | 117,285 |
A simple, efficient, and reliable helper for running scheduled tasks (cronjobs) in Bevy applications.
Add to your Cargo.toml:
[dependencies]
bevy_cronjob = "0.6"
Basic usage:
use bevy::prelude::*;
use bevy_cronjob::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(CronJobPlugin)
.add_systems(Startup, setup)
.add_systems(
Update,
my_system.run_if(schedule_passed("every 5 seconds")),
)
.run();
}
fn setup(mut commands: Commands) {
commands
.spawn(ScheduleTimer::new("every 10 seconds"))
.observe(|_: Trigger<ScheduleArrived>| {
info!("Timer triggered!");
});
}
fn my_system() {
info!("This runs every 5 seconds");
}
Perfect for simple, stateless scheduled tasks:
use bevy_cronjob::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_plugins(CronJobPlugin)
.add_systems(
Update,
(
save_game.run_if(schedule_passed("every 30 seconds")),
cleanup_cache.run_if(schedule_passed("every 5 minutes")),
daily_backup.run_if(schedule_passed("every day at 3 am")),
),
)
.run();
}
fn save_game() {
info!("Auto-saving game...");
}
fn cleanup_cache() {
info!("Cleaning up cache...");
}
fn daily_backup() {
info!("Running daily backup...");
}
Pros: Minimal setup, integrates seamlessly with Bevy's scheduling Cons: Each run condition maintains separate state
Ideal for complex scheduling needs with entity-specific logic:
use bevy_cronjob::prelude::*;
#[derive(Component)]
struct GameStats {
score: u32,
level: u32,
}
fn setup(mut commands: Commands) {
// Create entities with different schedules
commands
.spawn((
ScheduleTimer::new("every 1 minute"),
GameStats { score: 0, level: 1 },
Name::new("Score Reporter"),
))
.observe(report_score);
commands
.spawn((
ScheduleTimer::new("every 10 seconds"),
Name::new("Health Monitor"),
))
.observe(check_player_health);
}
fn report_score(
trigger: Trigger<ScheduleArrived>,
query: Query<&GameStats>,
) {
if let Ok(stats) = query.get(trigger.entity()) {
info!("Current score: {}, Level: {}", stats.score, stats.level);
}
}
fn check_player_health(trigger: Trigger<ScheduleArrived>) {
info!("Checking player health for entity: {:?}", trigger.entity());
}
Pros: Entity-specific data, flexible event handling, better for complex logic Cons: Slightly more setup required
Standard cron syntax based on the cron crate:
| Field | Values | Description |
|---|---|---|
| Second | 0-59 | Seconds |
| Minute | 0-59 | Minutes |
| Hour | 0-23 | Hours (24-hour format) |
| Day of Month | 1-31 | Day of the month |
| Month | 1-12 | Month (1=January, 12=December) |
| Day of Week | 1-7 | Day of the week (1=Monday) |
| Year | 1970-2100 | Year (optional) |
Special Characters:
* - Any value? - No specific value (for day fields)/ - Step values (e.g., 0/5 = every 5 units), - List separator (e.g., 1,3,5)- - Range (e.g., 1-5)Examples:
"0/5 * * * * ? *" // Every 5 seconds
"0 * * * * ? *" // Every minute at second 0
"0 0 * * * ? *" // Every hour at minute 0
"0 0 0 * * ? *" // Every day at midnight
"0 0 9 * * MON-FRI *" // Every weekday at 9 AM
"0 0 0 1 * ? *" // First day of every month
"0 30 14 * * ? *" // Every day at 2:30 PM
Natural language scheduling powered by english-to-cron:
| English Expression | Equivalent Cron | Description |
|---|---|---|
every 15 seconds |
0/15 * * * * ? * |
Every 15 seconds |
every minute |
0 * * * * ? * |
Every minute |
every hour |
0 0 * * * ? * |
Every hour |
every day |
0 0 0 */1 * ? * |
Every day at midnight |
every day at 4:00 pm |
0 0 16 */1 * ? * |
Every day at 4 PM |
at 10:00 am |
0 0 10 * * ? * |
Every day at 10 AM |
run at midnight on the 1st and 15th of month |
0 0 0 1,15 * ? * |
1st and 15th of each month |
On Sunday at 12:00 |
0 0 12 ? * SUN * |
Every Sunday at noon |
7pm every Thursday |
0 0 19 ? * THU * |
Every Thursday at 7 PM |
midnight on Tuesdays |
0 0 0 ? * TUE * |
Every Tuesday at midnight |
Common schedules are available as constants for convenience:
use bevy_cronjob::*;
// Frequent intervals
EVERY_5_SEC // "0/5 * * * * ? *"
EVERY_10_SEC // "0/10 * * * * ? *"
EVERY_30_SEC // "0/30 * * * * ? *"
EVERY_MIN // "0 * * * * ? *"
EVERY_5_MIN // "0 0/5 * * * ? *"
EVERY_30_MIN // "0 0/30 * * * ? *"
EVERY_HOUR // "0 0 * * * ? *"
EVERY_DAY // "0 0 0 */1 * ? *"
// Specific daily times
EVERY_1_AM // "0 0 1 */1 * ? *"
EVERY_6_AM // "0 0 6 */1 * ? *"
EVERY_12_PM // "0 0 12 */1 * ? *"
EVERY_6_PM // "0 0 18 */1 * ? *"
EVERY_11_PM // "0 0 23 */1 * ? *"
// ... and many more
Usage:
.add_systems(
Update,
backup_system.run_if(schedule_passed(EVERY_DAY)),
)
use bevy_cronjob::prelude::*;
#[derive(Component)]
struct Enemy;
#[derive(Component)]
struct SpawnTimer;
fn setup_game_schedules(mut commands: Commands) {
// Spawn enemies every 30 seconds
commands
.spawn((
ScheduleTimer::new("every 30 seconds"),
SpawnTimer,
Name::new("Enemy Spawner"),
))
.observe(spawn_enemy_wave);
// Save game progress every 5 minutes
commands
.spawn(ScheduleTimer::new("every 5 minutes"))
.observe(|_: Trigger<ScheduleArrived>| {
info!("Auto-saving game progress...");
// Save game logic here
});
// Daily challenges reset at midnight
commands
.spawn(ScheduleTimer::new("every day at 12 am"))
.observe(reset_daily_challenges);
// Weekend bonus events
commands
.spawn(ScheduleTimer::new("0 0 18 ? * FRI *")) // Friday 6 PM
.observe(|_: Trigger<ScheduleArrived>| {
info!("Weekend bonus event started!");
});
}
fn spawn_enemy_wave(
trigger: Trigger<ScheduleArrived>,
mut commands: Commands,
) {
info!("Spawning enemy wave for spawner: {:?}", trigger.entity());
// Spawn multiple enemies
for i in 0..5 {
commands.spawn((
Enemy,
Name::new(format!("Enemy-{}", i)),
// Add enemy components...
));
}
}
fn reset_daily_challenges(trigger: Trigger<ScheduleArrived>) {
info!("Resetting daily challenges...");
// Reset challenge progress
}
use bevy_cronjob::prelude::*;
fn setup_server_maintenance(mut commands: Commands) {
// Log server stats every minute
commands
.spawn(ScheduleTimer::new("every minute"))
.observe(log_server_stats);
// Clean up disconnected players every 5 minutes
commands
.spawn(ScheduleTimer::new("every 5 minutes"))
.observe(cleanup_disconnected_players);
// Database backup every day at 3 AM
commands
.spawn(ScheduleTimer::new("every day at 3 am"))
.observe(backup_database);
// Weekly server restart (Sunday 4 AM)
commands
.spawn(ScheduleTimer::new("0 0 4 ? * SUN *"))
.observe(schedule_server_restart);
}
fn log_server_stats(trigger: Trigger<ScheduleArrived>) {
info!("Server uptime check - Entity: {:?}", trigger.entity());
// Log memory usage, player count, etc.
}
fn cleanup_disconnected_players(trigger: Trigger<ScheduleArrived>) {
info!("Cleaning up disconnected players...");
// Remove inactive player entities
}
fn backup_database(trigger: Trigger<ScheduleArrived>) {
info!("Starting database backup...");
// Backup logic
}
fn schedule_server_restart(trigger: Trigger<ScheduleArrived>) {
info!("Scheduling server restart for maintenance...");
// Graceful restart logic
}
use bevy_cronjob::prelude::*;
#[derive(Component)]
struct DifficultyLevel(u32);
fn setup_dynamic_scheduling(mut commands: Commands) {
commands.spawn((
DifficultyLevel(1),
Name::new("Game Manager"),
));
}
fn adjust_spawn_rate(
mut commands: Commands,
query: Query<(Entity, &DifficultyLevel)>,
existing_timers: Query<Entity, With<ScheduleTimer>>,
) {
for (entity, difficulty) in query.iter() {
// Remove old spawn timers
for timer_entity in existing_timers.iter() {
commands.entity(timer_entity).despawn();
}
// Create new timer based on difficulty
let spawn_interval = match difficulty.0 {
1 => "every 30 seconds",
2 => "every 20 seconds",
3 => "every 10 seconds",
4 => "every 5 seconds",
_ => "every 2 seconds",
};
commands
.spawn(ScheduleTimer::new(spawn_interval))
.observe(move |_: Trigger<ScheduleArrived>| {
info!("Spawning enemies at difficulty level {}", difficulty.0);
});
}
}
use cron::Schedule;
use std::str::FromStr;
use bevy_cronjob::*;
fn safe_schedule_creation(expression: &str) -> Result<ScheduleTimer, String> {
// Parse expression first (handles English to cron conversion)
let cron_expr = if expression.chars().any(|c| c.is_ascii_alphabetic()) {
str_cron_syntax(expression)
.map_err(|e| format!("Invalid English expression '{}': {}", expression, e))?
} else {
expression.to_string()
};
// Validate cron expression
Schedule::from_str(&cron_expr)
.map_err(|e| format!("Invalid cron expression '{}': {}", cron_expr, e))?;
Ok(ScheduleTimer::new(expression))
}
fn setup_with_validation(mut commands: Commands) {
match safe_schedule_creation("every 5 seconds") {
Ok(timer) => {
commands
.spawn(timer)
.observe(|_: Trigger<ScheduleArrived>| {
info!("Safe timer triggered!");
});
}
Err(e) => {
error!("Failed to create schedule: {}", e);
}
}
}
use bevy_cronjob::prelude::*;
#[derive(Component)]
struct ScheduleConfig {
expression: String,
enabled: bool,
}
fn manage_schedules_system(
mut commands: Commands,
config_query: Query<(Entity, &ScheduleConfig), Changed<ScheduleConfig>>,
timer_query: Query<Entity, With<ScheduleTimer>>,
) {
for (entity, config) in config_query.iter() {
// Remove existing timer if any
if let Ok(timer_entity) = timer_query.get(entity) {
commands.entity(timer_entity).despawn();
}
// Add new timer if enabled
if config.enabled {
match safe_schedule_creation(&config.expression) {
Ok(timer) => {
commands.entity(entity).insert(timer);
}
Err(e) => {
warn!("Invalid schedule for entity {:?}: {}", entity, e);
}
}
}
}
}
The crate includes several performance optimizations for production use:
The crate includes comprehensive tests. Run them with:
cargo test
For manual testing, run the examples:
# Basic functionality
cargo run --example cronjobs
# Performance demonstration
cargo run --example performance_demo
# Trigger testing
cargo run --example trigger_test
No breaking changes! The API remains the same, but with important improvements:
Simply update your Cargo.toml:
[dependencies]
bevy_cronjob = "0.6"
| Bevy Version | bevy_cronjob Version |
|---|---|
| 0.16 | 0.6 |
| 0.15 | 0.5 |
| 0.14 | 0.4 |
| 0.13 | 0.3 |
Contributions are welcome! Here's how you can help:
git clone https://github.com/foxzool/bevy_cronjob.git
cd bevy_cronjob
cargo test
cargo run --example cronjobs
This project is dual-licensed under either:
You may choose either license when using this crate in your projects.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
Made with โค๏ธ for the Bevy community