R/resource-caching_layer.R

#' Cache a resource's parsed value.
#'
#' More complex resources are typically time-consuming to compile, or
#' are unnecessary to compile more than once per R session.
#'
#' The \code{caching_layer} provides an internal caching mechanism that
#' will remember the value of the parsed resource and re-use it
#' \emph{unless the resource or any of its dependencies have been
#' modified} (see \link{dependency_tracking} for an explanation of
#' how this is accomplished).
#'
#' @name resource caching
#' @aliases caching_layer
#' @param object active_resource. See \code{\link{active_resource}}.
#' @param ... additional parameters to pass to the next layer in the resource
#'    parsing tower.
#' @param recompile. logical. Whether or not to force the resource to
#'    be recompiled (instead of retrieved from cache), regardless of
#'    whether the resource or any of its dependencies have been modified.
#' @seealso \code{\link{active_resource}}, \code{\link{tower}}
#' @return The parsed resource, retrieved from cache since the last time
#'    the resource was executed if and only if the resource's file(s) and
#'    none of its (recursive) dependencies have been modified.
#' @note The parameters must be named \code{object} and \code{...} due to
#'    this method's inclusion in a \code{\link{tower}}.
#' @examples
#' \dontrun{
#'   # Imagine we are constructing a stagerunner from a sequence of functions.
#'   # However, some of those functions have been built by other resources.
#'   # Imagine the following structure.
#'   # (See github.com/robertzk/stagerunner for an explanation of stagerunners.)
#'
#'   #=== /dir/runners/project1.R ===
#'   list(
#'     "import data"  = resource("importers/db"),   # These are some functions
#'     "munge data"   = resource("mungers/impute"), # built by the user
#'     "create model" = resource("models/lm"),      # that live in other
#'     "export model" = resource("exporters/file")  # files.
#'   )
#'
#'   #=== R console ===
#'   d <- director("/dir") # Create a director object.
#'   d$register_parser("runners/", function(output) {
#'     stagerunner::stageRunner$new(new.env(), output)
#'   }, cache = TRUE) # Note the cache = TRUE argument.
#'
#'   sr  <- d$resource("runners/project1") # A fresh new stageRunner!
#'   sr2 <- d$resource("runners/project1") # Same one, since it used the cache.
#'   stopifnot(identical(sr, sr2))
#'
#'   # We can use base::Sys.setFileTime to pretend like we updated the
#'   # modified time of the /dir/connections/dev.R file, triggering
#'   # the caching layer to re-compile the resource.
#'   Sys.setFileTime(file.path(d$root(), "runners", "project1.R"),
#'     Sys.time() - as.difftime(1, units = "mins"))
#'
#'   sr3 <- d$resource("runners/project1") # Now it re-builds the runner.
#'   stopifnot(!identical(sr, sr3)) # A new runner, with hardly any work!
#' }
caching_layer <- function(object, ..., recompile. = FALSE) {
  # Ideally, caching should be as smart as preprocessor/parser routing
  # but this is a cheap way to do it now.
  caching_enabled <- any_is_substring_of(
    object$resource$name,
    object$resource$director$cached_resources()
  )

  if (!caching_enabled) {
    ## We skip the caching layer completely if this is not a cached resource.
    yield()
  } else {
    ## If this resource has been parsed before but any of its dependencies
    ## have been modified, we should wipe the cache.
    is_cached <- 
      !isTRUE(recompile.) && 
      !isTRUE(object$injects$any_dependencies_modified) &&
      ## This will exist if and only if the resource has been parsed and its
      ## value has been stored before.
      base::exists("caching_layer.value", envir = object$state)

    if (is_cached) {
      ## This is used down the road by the `stop_tracking_dependencies`
      ## helper of the `dependency_tracker`.
      object$injects$cache_used <- TRUE
      object$state$caching_layer.value
    } else {
      ## Note that wrapping an expression in parentheses in R will prevent
      ## its output from becoming [invisible](http://stackoverflow.com/questions/11653127/what-does-the-function-invisible-do).
      (object$state$caching_layer.value <- yield())
    }
  }
}
syberia/director documentation built on May 30, 2019, 10:40 p.m.