# Return the current time, in milliseconds from epoch.
getTimeMs <- function() {
as.numeric(Sys.time()) * 1000
}
TimerCallbacks <- R6Class(
'TimerCallbacks',
portable = FALSE,
class = FALSE,
public = list(
.nextId = 0L,
.funcs = 'Map',
.times = data.frame(),
.now = 'Function',
initialize = function(nowFn = getTimeMs) {
.funcs <<- Map$new()
.now <<- nowFn
},
clear = function() {
.nextId <<- 0L
.funcs$clear()
.times <<- data.frame()
},
schedule = function(millis, func) {
# If args could fail to evaluate, let's make them do that before
# we change any state
force(millis)
force(func)
id <- .nextId
.nextId <<- .nextId + 1L
t <- .now()
# TODO: Horribly inefficient, use a heap instead
.times <<- rbind(.times, data.frame(time=t+millis,
scheduled=t,
id=id))
.times <<- .times[order(.times$time),]
.funcs$set(as.character(id), func)
return(id)
},
unschedule = function(id) {
toRemoveIndices <- .times$id %in% id
toRemoveIds <- .times[toRemoveIndices, "id", drop = TRUE]
if (length(toRemoveIds) > 0) {
.times <<- .times[!toRemoveIndices,]
for (toRemoveId in as.character(toRemoveIds)) {
.funcs$remove(toRemoveId)
}
}
return(id %in% toRemoveIds)
},
timeToNextEvent = function() {
if (dim(.times)[1] == 0)
return(Inf)
return(.times[1, 'time'] - .now())
},
takeElapsed = function() {
t <- .now()
elapsed <- .times$time <= .now()
result <- .times[elapsed,]
.times <<- .times[!elapsed,]
# TODO: Examine scheduled column to check if any funny business
# has occurred with the system clock (e.g. if scheduled
# is later than .now())
return(result)
},
executeElapsed = function() {
elapsed <- takeElapsed()
if (nrow(elapsed) == 0)
return(FALSE)
for (id in elapsed$id) {
thisFunc <- .funcs$remove(as.character(id))
# TODO: Catch exception, and...?
# TODO: Detect NULL, and...?
thisFunc()
}
return(TRUE)
}
)
)
MockableTimerCallbacks <- R6Class(
'MockableTimerCallbacks',
inherit = TimerCallbacks,
portable = FALSE,
class = FALSE,
public = list(
# Empty constructor defaults to the getNow implementation
initialize = function() {
super$initialize(self$mockNow)
},
mockNow = function() {
return(private$time)
},
elapse = function(millis) {
private$time <- private$time + millis
},
getElapsed = function() {
private$time
}
), private = list(
time = 0L
)
)
timerCallbacks <- TimerCallbacks$new()
scheduleTask <- function(millis, callback) {
cancelled <- FALSE
id <- timerCallbacks$schedule(millis, callback)
function() {
invisible(timerCallbacks$unschedule(id))
}
}
#' Get a scheduler function for scheduling tasks. Give priority to the
#' session scheduler, but if it doesn't exist, use the global one.
#' @noRd
defineScheduler <- function(session){
if (!is.null(session) && !is.null(session$.scheduleTask)){
return(session$.scheduleTask)
}
scheduleTask
}
#' Get the current time using the current reactive domain. This will try to use
#' the session's .now() method, but if that's not available, it will just return
#' the real time (from getTimeMs()). The purpose of this function is to allow
#' MockableTimerCallbacks to work.
#' @noRd
getDomainTimeMs <- function(session){
if (!is.null(session) && !is.null(session$.now)){
return(session$.now())
} else {
getTimeMs()
}
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.