@@ -79,6 +79,8 @@ Timer <- R6::R6Class(
7979 # ' - `where` `expr` filter conditions in [dplyr::filter()] style
8080 # ' - `analysis` `function` or `NULL` analysis applied to filtered data
8181 # ' - `name` `character` or `NULL` unique key for the condition
82+ # ' - `cooldown` `numeric` minimum time between consecutive triggers
83+ # ' - `max_triggers` `integer` maximum number of times this condition can trigger
8284 conditions = NULL ,
8385
8486 # --- constructor ---
@@ -134,6 +136,8 @@ Timer <- R6::R6Class(
134136 # ' @param ... `expression` Boolean expression(s) for `dplyr::filter()`.
135137 # ' @param analysis `function` or `NULL` Optional function to apply.
136138 # ' @param name `character` Unique condition identifier.
139+ # ' @param cooldown `numeric` Minimum time between consecutive triggers (default: 0, no cooldown).
140+ # ' @param max_triggers `integer` Maximum number of times this condition can trigger (default: 1, single trigger).
137141 # '
138142 # ' @examples
139143 # ' #' t <- Timer$new(name = "Timer")
@@ -166,15 +170,35 @@ Timer <- R6::R6Class(
166170 add_condition = function (
167171 ... ,
168172 analysis = NULL ,
169- name = NULL
173+ name = NULL ,
174+ cooldown = 0 ,
175+ max_triggers = 1L
176+
170177 ) {
171178 # Capture filter predicates as quosures (with caller env)
172179 where_quos <- rlang :: enquos(... , .named = FALSE )
173180
181+ # Variable Checks and Error catching
182+ cooldown <- as.numeric(cooldown )
183+ if (length(cooldown ) != 1L || is.na(cooldown ) || cooldown < 0 ) {
184+ stop(" `cooldown` must be a single non-negative number." )
185+ }
186+
187+ max_triggers <- as.integer(max_triggers )
188+ if (length(max_triggers ) != 1L || is.na(max_triggers ) || max_triggers < 0 ) {
189+ stop(" `max_triggers` must be a single non-negative integer (use Inf for unlimited)." )
190+ }
191+
192+
174193 cond <- list (
175194 where = where_quos ,
176195 analysis = analysis ,
177- name = name
196+ name = name ,
197+ cooldown = cooldown ,
198+ max_triggers = max_triggers ,
199+ trigger_count = 0L ,
200+ last_trigger_time = NA_real_
201+
178202 )
179203 self $ conditions <- append(self $ conditions , list (cond ))
180204 invisible (self )
@@ -322,17 +346,51 @@ Timer <- R6::R6Class(
322346 locked_data
323347 }
324348
325- if (nrow(df_i ) == 0L ) {
326- # warning(sprintf("Skipping condition '%s' at time %s: filtered data is empty", key, as.character(current_time)), call. = FALSE)
349+
350+ if (is.null(cond $ trigger_count )) cond $ trigger_count <- 0L
351+ if (is.null(cond $ max_triggers )) cond $ max_triggers <- 1L
352+ if (is.null(cond $ last_trigger_time )) cond $ last_trigger_time <- NA_real_
353+ if (is.null(cond $ cooldown )) cond $ cooldown <- 0
354+
355+ match_now <- nrow(df_i ) > 0L
356+
357+
358+ # If no match, skip
359+ if (! match_now ) {
360+ self $ conditions [[i ]] <- cond
327361 next
328362 }
329363
364+
365+ # Hard cap on number of triggers
366+ if (is.finite(cond $ max_triggers ) && cond $ trigger_count > = cond $ max_triggers ) {
367+ self $ conditions [[i ]] <- cond
368+ next
369+ }
370+
371+ # Check cooldown
372+ if (is.finite(cond $ last_trigger_time )) {
373+ if ((current_time - cond $ last_trigger_time ) < cond $ cooldown ) {
374+ self $ conditions [[i ]] <- cond
375+ next
376+ }
377+ }
378+
379+
330380 if (is.function(cond $ analysis )) {
331381 results [[key ]] <- cond $ analysis(df_i , current_time )
332382 } else {
333383 results [[key ]] <- df_i
334384 warning(sprintf(" returning filtered data as is because condition '%s' has no applicable analysis \n " , key ), call. = FALSE )
335385 }
386+
387+ # Update trigger info after a successful trigger
388+ cond $ trigger_count <- cond $ trigger_count + 1L
389+ cond $ last_trigger_time <- current_time
390+
391+ # Persist state back into Timer
392+ self $ conditions [[i ]] <- cond
393+
336394 }
337395
338396 results
0 commit comments