Skip to content

Commit 9045bc5

Browse files
committed
add component for observer
1 parent fd2c68b commit 9045bc5

File tree

4 files changed

+114
-27
lines changed

4 files changed

+114
-27
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ homepage = "https://github.com/foxzool/bevy_cronjob"
1313
documentation = "https://docs.rs/bevy_cronjob"
1414

1515
[dependencies]
16+
bevy_app = { version = "0.15.0" }
1617
bevy_ecs = { version = "0.15.0" }
1718

1819
cron = "0.13.0"

examples/cronjobs.rs

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,9 @@
1+
use bevy::log::LogPlugin;
2+
use bevy::prelude::*;
3+
use bevy_app::ScheduleRunnerPlugin;
4+
use bevy_cronjob::prelude::*;
15
use std::time::Duration;
26

3-
use bevy::{
4-
app::{App, PluginGroup, ScheduleRunnerPlugin, Update},
5-
log::{info, LogPlugin},
6-
MinimalPlugins,
7-
};
8-
use bevy_ecs::prelude::IntoSystemConfigs;
9-
10-
use bevy_cronjob::schedule_passed;
11-
127
fn main() {
138
App::new()
149
.add_plugins(
@@ -17,6 +12,8 @@ fn main() {
1712
))),
1813
)
1914
.add_plugins(LogPlugin::default())
15+
.add_plugins(CronJobPlugin)
16+
.add_systems(Startup, setup)
2017
.add_systems(
2118
Update,
2219
print_per_5_sec.run_if(schedule_passed("every 5 seconds")),
@@ -30,13 +27,21 @@ fn main() {
3027
}
3128

3229
fn print_per_5_sec() {
33-
info!("print every 5 sec")
30+
info!("system run every 5 sec")
3431
}
3532

3633
fn print_per_min() {
37-
info!("print every minute")
34+
info!("system run every minute")
3835
}
3936

4037
fn print_per_hour() {
41-
info!("print every hour")
38+
info!("system run every hour")
39+
}
40+
41+
fn setup(mut commands: Commands) {
42+
commands
43+
.spawn(ScheduleTimer::new("every 3 seconds"))
44+
.observe(|_: Trigger<ScheduleArrived>| {
45+
info!("3 seconds passed");
46+
});
4247
}

src/lib.rs

Lines changed: 95 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,19 @@
1-
use std::str::FromStr;
2-
3-
use bevy_ecs::prelude::Local;
1+
use bevy_app::prelude::*;
2+
use bevy_ecs::prelude::*;
43
use chrono::DateTime;
54
use cron::Schedule;
65
pub use english_to_cron::str_cron_syntax;
6+
use std::str::FromStr;
77

88
/// bevy_cronjob is a simple helper to run cronjobs (at repeated schedule) in Bevy.
99
/// # Usage
1010
///
1111
/// ``` rust,no_run
12+
/// use bevy::log::LogPlugin;
13+
/// use bevy::prelude::*;
14+
/// use bevy_app::ScheduleRunnerPlugin;
15+
/// use bevy_cronjob::prelude::*;
1216
/// use std::time::Duration;
13-
/// use bevy::{ MinimalPlugins};
14-
/// use bevy::app::{App, PluginGroup, ScheduleRunnerPlugin, Update};
15-
/// use bevy::log::{info, LogPlugin};
16-
///
17-
/// use bevy_ecs::prelude::{IntoSystemConfigs};
18-
/// use bevy_cronjob::schedule_passed;
1917
///
2018
/// fn main() {
2119
/// App::new()
@@ -25,6 +23,7 @@ pub use english_to_cron::str_cron_syntax;
2523
/// ))),
2624
/// )
2725
/// .add_plugins(LogPlugin::default())
26+
/// .add_plugins(CronJobPlugin)
2827
/// .add_systems(Update, print_per_5_sec.run_if(schedule_passed("0/5 * * * ? *")))
2928
/// .add_systems(Update, print_per_min.run_if(schedule_passed("0 * * * ? *")))
3029
/// .add_systems(Update, print_per_hour.run_if(schedule_passed("0 0 * * ? *")))
@@ -41,6 +40,15 @@ pub use english_to_cron::str_cron_syntax;
4140
/// fn print_per_hour() {
4241
/// info!("print every hour")
4342
/// }
43+
///
44+
/// fn setup(mut commands: Commands) {
45+
/// commands
46+
/// .spawn(ScheduleTimer::new("every 3 seconds"))
47+
/// .observe(|_: Trigger<ScheduleArrived>| {
48+
/// info!("3 seconds passed");
49+
/// });
50+
/// }
51+
///
4452
/// ```
4553
///
4654
/// ## Expression
@@ -179,13 +187,9 @@ pub const EVERY_12_AM: &str = "0 0 0 */1 * ? *";
179187
pub fn schedule_passed(
180188
expression: &str,
181189
) -> impl FnMut(Local<Option<DateTime<chrono::Local>>>) -> bool {
182-
let new_expression = if expression.chars().any(|c| c.is_ascii_alphabetic()) {
183-
str_cron_syntax(expression).expect("Failed to parse cron expression")
184-
} else {
185-
expression.to_string()
186-
};
190+
let expression = try_english_pattern(expression);
187191

188-
let schedule = Schedule::from_str(&new_expression).expect("Failed to parse cron expression");
192+
let schedule = Schedule::from_str(&expression).expect("Failed to parse cron expression");
189193
move |mut local_schedule: Local<Option<DateTime<chrono::Local>>>| {
190194
if let Some(datetime) = schedule.upcoming(chrono::Local).next() {
191195
let now = chrono::Local::now();
@@ -205,6 +209,82 @@ pub fn schedule_passed(
205209
}
206210
}
207211

212+
/// A Bevy plugin for running cron jobs
213+
pub struct CronJobPlugin;
214+
215+
impl Plugin for CronJobPlugin {
216+
fn build(&self, app: &mut App) {
217+
app.add_systems(Update, check_schedule_timer);
218+
}
219+
}
220+
221+
/// A component that holds a cron expression
222+
#[derive(Debug, Component)]
223+
pub struct ScheduleTimer {
224+
pub schedule: Schedule,
225+
pub local_schedule: Option<DateTime<chrono::Local>>,
226+
}
227+
228+
impl ScheduleTimer {
229+
pub fn new(expression: &str) -> Self {
230+
let expression = try_english_pattern(expression);
231+
232+
let schedule = Schedule::from_str(&expression).expect("Failed to parse cron expression");
233+
Self {
234+
schedule,
235+
local_schedule: None,
236+
}
237+
}
238+
239+
fn schedule_passed(&mut self) -> bool {
240+
if let Some(datetime) = self.schedule.upcoming(chrono::Local).next() {
241+
let now = chrono::Local::now();
242+
match self.local_schedule {
243+
Some(local) => {
244+
if now > local {
245+
self.local_schedule = Some(datetime);
246+
return true;
247+
}
248+
}
249+
250+
None => self.local_schedule = Some(datetime),
251+
}
252+
}
253+
254+
false
255+
}
256+
}
257+
258+
fn try_english_pattern(expression: &str) -> String {
259+
if expression.chars().any(|c| c.is_ascii_alphabetic()) {
260+
str_cron_syntax(expression).expect("Failed to parse cron expression")
261+
} else {
262+
expression.to_string()
263+
}
264+
}
265+
266+
/// A system that checks if the cron expression has passed
267+
fn check_schedule_timer(mut query: Query<(Entity, &mut ScheduleTimer)>, mut commands: Commands) {
268+
let mut targets = vec![];
269+
270+
for (entity, mut schedule_timer) in query.iter_mut() {
271+
if schedule_timer.schedule_passed() {
272+
targets.push(entity);
273+
}
274+
}
275+
276+
if !targets.is_empty() {
277+
commands.trigger_targets(ScheduleArrived, targets);
278+
}
279+
}
280+
281+
#[derive(Event)]
282+
pub struct ScheduleArrived;
283+
284+
pub mod prelude {
285+
pub use crate::{schedule_passed, CronJobPlugin, ScheduleArrived, ScheduleTimer};
286+
}
287+
208288
#[test]
209289
fn test_expression() {
210290
assert_eq!(EVERY_5_SEC, str_cron_syntax("every 5 seconds").unwrap());

0 commit comments

Comments
 (0)