1
- use std:: str:: FromStr ;
2
-
3
- use bevy_ecs:: prelude:: Local ;
1
+ use bevy_app:: prelude:: * ;
2
+ use bevy_ecs:: prelude:: * ;
4
3
use chrono:: DateTime ;
5
4
use cron:: Schedule ;
6
5
pub use english_to_cron:: str_cron_syntax;
6
+ use std:: str:: FromStr ;
7
7
8
8
/// bevy_cronjob is a simple helper to run cronjobs (at repeated schedule) in Bevy.
9
9
/// # Usage
10
10
///
11
11
/// ``` rust,no_run
12
+ /// use bevy::log::LogPlugin;
13
+ /// use bevy::prelude::*;
14
+ /// use bevy_app::ScheduleRunnerPlugin;
15
+ /// use bevy_cronjob::prelude::*;
12
16
/// 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;
19
17
///
20
18
/// fn main() {
21
19
/// App::new()
@@ -25,6 +23,7 @@ pub use english_to_cron::str_cron_syntax;
25
23
/// ))),
26
24
/// )
27
25
/// .add_plugins(LogPlugin::default())
26
+ /// .add_plugins(CronJobPlugin)
28
27
/// .add_systems(Update, print_per_5_sec.run_if(schedule_passed("0/5 * * * ? *")))
29
28
/// .add_systems(Update, print_per_min.run_if(schedule_passed("0 * * * ? *")))
30
29
/// .add_systems(Update, print_per_hour.run_if(schedule_passed("0 0 * * ? *")))
@@ -41,6 +40,15 @@ pub use english_to_cron::str_cron_syntax;
41
40
/// fn print_per_hour() {
42
41
/// info!("print every hour")
43
42
/// }
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
+ ///
44
52
/// ```
45
53
///
46
54
/// ## Expression
@@ -179,13 +187,9 @@ pub const EVERY_12_AM: &str = "0 0 0 */1 * ? *";
179
187
pub fn schedule_passed (
180
188
expression : & str ,
181
189
) -> 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) ;
187
191
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" ) ;
189
193
move |mut local_schedule : Local < Option < DateTime < chrono:: Local > > > | {
190
194
if let Some ( datetime) = schedule. upcoming ( chrono:: Local ) . next ( ) {
191
195
let now = chrono:: Local :: now ( ) ;
@@ -205,6 +209,82 @@ pub fn schedule_passed(
205
209
}
206
210
}
207
211
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
+
208
288
#[ test]
209
289
fn test_expression ( ) {
210
290
assert_eq ! ( EVERY_5_SEC , str_cron_syntax( "every 5 seconds" ) . unwrap( ) ) ;
0 commit comments