-
Notifications
You must be signed in to change notification settings - Fork 35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Move all plot watching logic into watchout #151
Changes from 8 commits
796e6e9
36628ba
be153f8
1754e24
0748e3c
a7bd643
07a4f7a
d0ac039
27e3983
0b27207
f39fbcd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,40 +69,18 @@ evaluate <- function(input, | |
} | ||
local_inject_funs(envir) | ||
|
||
if (new_device) { | ||
# Ensure we have a graphics device available for recording, but choose | ||
# one that's available on all platforms and doesn't write to disk | ||
pdf(file = NULL) | ||
dev.control(displaylist = "enable") | ||
dev <- dev.cur() | ||
on.exit(dev.off(dev)) | ||
} | ||
# record the list of current devices | ||
devs <- .env$dev_list; on.exit(.env$dev_list <- devs, add = TRUE) | ||
devn <- length(.env$dev_list <- dev.list()) | ||
dev <- dev.cur() | ||
|
||
# clean up the last_plot object after an evaluate() call (cf yihui/knitr#722) | ||
on.exit(assign("last_plot", NULL, envir = environment(plot_snapshot)), add = TRUE) | ||
|
||
# if this env var is set to true, always bypass messages | ||
if (tolower(Sys.getenv('R_EVALUATE_BYPASS_MESSAGES')) == 'true') | ||
keep_message = keep_warning = NA | ||
|
||
# Capture output | ||
watcher <- watchout(output_handler, debug = debug) | ||
watcher <- watchout(output_handler, new_device = new_device, debug = debug) | ||
|
||
out <- vector("list", nrow(parsed)) | ||
for (i in seq_along(out)) { | ||
if (debug) { | ||
message(parsed$src[[i]]) | ||
} | ||
|
||
# if dev.off() was called, make sure to restore device to the one opened by | ||
# evaluate() or existed before evaluate() | ||
if (length(dev.list()) < devn) dev.set(dev) | ||
devn <- length(dev.list()) | ||
|
||
out[[i]] <- evaluate_top_level_expression( | ||
exprs = parsed$expr[[i]], | ||
src = parsed$src[[i]], | ||
|
@@ -117,6 +95,7 @@ evaluate <- function(input, | |
output_handler = output_handler, | ||
include_timing = include_timing | ||
) | ||
watcher$check_devices() | ||
|
||
if (stop_on_error > 0L) { | ||
errs <- vapply(out[[i]], is.error, logical(1)) | ||
|
@@ -154,12 +133,12 @@ evaluate_top_level_expression <- function(exprs, | |
source <- new_source(src, exprs[[1]], output_handler$source) | ||
output <- list(source) | ||
|
||
dev <- dev.cur() | ||
handle_output <- function(plot = TRUE, incomplete_plots = FALSE) { | ||
# if dev.cur() has changed, we should not record plots any more | ||
plot <- plot && identical(dev, dev.cur()) | ||
out <- watcher(plot, incomplete_plots) | ||
output <<- c(output, out) | ||
out <- list( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't like this interface so it's likely to change in a future PR. |
||
if (plot) watcher$capture_plot(incomplete_plots), | ||
watcher$capture_output() | ||
) | ||
output <<- c(output, compact(out)) | ||
} | ||
|
||
local_output_handler(function() handle_output(FALSE)) | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -1,15 +1,23 @@ | ||||||||||||||||
#' Watch for changes in output, text and graphics | ||||||||||||||||
#' | ||||||||||||||||
#' @param debug activate debug mode where output will be both printed to | ||||||||||||||||
#' screen and captured. | ||||||||||||||||
#' @param handler An ouptut handler object. | ||||||||||||||||
#' @param frame When this frame terminates, the watcher will automatically close.` | ||||||||||||||||
#' @return list containing four functions: `get_new`, `pause`, | ||||||||||||||||
#' `unpause`, `close`. | ||||||||||||||||
#' @keywords internal | ||||||||||||||||
watchout <- function(handler = new_output_handler(), | ||||||||||||||||
new_device = TRUE, | ||||||||||||||||
debug = FALSE, | ||||||||||||||||
frame = parent.frame()) { | ||||||||||||||||
last_plot <- NULL | ||||||||||||||||
|
||||||||||||||||
if (new_device) { | ||||||||||||||||
# Ensure we have a graphics device available for recording, but choose | ||||||||||||||||
# one that's available on all platforms and doesn't write to disk | ||||||||||||||||
pdf(file = NULL) | ||||||||||||||||
dev.control(displaylist = "enable") | ||||||||||||||||
dev <- dev.cur() | ||||||||||||||||
defer(dev.off(dev), frame) | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
# record current devices | ||||||||||||||||
devs <- dev.list() | ||||||||||||||||
devn <- length(devs) | ||||||||||||||||
dev <- dev.cur() | ||||||||||||||||
|
||||||||||||||||
con <- file("", "w+b") | ||||||||||||||||
defer(frame = frame, { | ||||||||||||||||
if (!test_con(con, isOpen)) { | ||||||||||||||||
|
@@ -24,20 +32,63 @@ watchout <- function(handler = new_output_handler(), | |||||||||||||||
old <- options(try.outFile = con) | ||||||||||||||||
defer(options(old), frame = frame) | ||||||||||||||||
|
||||||||||||||||
function(plot = TRUE, incomplete_plots = FALSE) { | ||||||||||||||||
out <- list( | ||||||||||||||||
if (plot) plot_snapshot(incomplete_plots), | ||||||||||||||||
read_con(con) | ||||||||||||||||
) | ||||||||||||||||
if (!is.null(out[[1]])) { | ||||||||||||||||
handler$graphics(out[[1]]) | ||||||||||||||||
capture_plot <- function(incomplete = FALSE) { | ||||||||||||||||
# only record plots for our graphics device | ||||||||||||||||
if (!identical(dev.cur(), dev)) { | ||||||||||||||||
return() | ||||||||||||||||
} | ||||||||||||||||
# cur_devs <- dev.list() | ||||||||||||||||
# # No graphics devices | ||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @cderv I left this code in but commented out to deliberately draw your attention to it. I'm pretty sure the logic is redundant (because if the device list has changed then that implies the current device has changed) and it doesn't cause any tests to fail. I think it was included because the logic was previously split over two functions and so it was harder to see that it was redundant. But it is a change and I wanted to double check that I didn't miss anything. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree this seems redundant. It seems all this have been introduced over time to fix different bugs. Some have tests that are passing, and other may not have test. I am seeing that Lines 157 to 163 in e8bd6f5
Now we call it once when creating I tried to see if this could cause problems, but it seems not. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I'm reasonably certain that I got that logic right, and I added a few more tests to capture various situations. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW in general if you're trying to find problems it's useful to consider the distinction between |
||||||||||||||||
# if (is.null(cur_devs)) { | ||||||||||||||||
# return() | ||||||||||||||||
# } | ||||||||||||||||
# # Current graphics device changed since evaluate started | ||||||||||||||||
# if (!identical(cur_devs, devs)) { | ||||||||||||||||
# return() | ||||||||||||||||
# } | ||||||||||||||||
|
||||||||||||||||
# current page is incomplete | ||||||||||||||||
if (!par("page") && !incomplete) { | ||||||||||||||||
return() | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
plot <- recordPlot() | ||||||||||||||||
if (!makes_visual_change(plot[[1]])) { | ||||||||||||||||
return() | ||||||||||||||||
} | ||||||||||||||||
if (!is.null(out[[2]])) { | ||||||||||||||||
handler$text(out[[2]]) | ||||||||||||||||
|
||||||||||||||||
if (!looks_different(last_plot[[1]], plot[[1]])) { | ||||||||||||||||
return() | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
last_plot <<- plot | ||||||||||||||||
handler$graphics(plot) | ||||||||||||||||
plot | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
capture_output <- function() { | ||||||||||||||||
out <- read_con(con) | ||||||||||||||||
if (!is.null(out)) { | ||||||||||||||||
handler$text(out) | ||||||||||||||||
} | ||||||||||||||||
out | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
check_devices <- function() { | ||||||||||||||||
# if dev.off() was called, make sure to restore device to the one opened | ||||||||||||||||
# when watchout() was called | ||||||||||||||||
if (length(dev.list()) < devn) { | ||||||||||||||||
dev.set(dev) | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
compact(out) | ||||||||||||||||
devn <<- length(dev.list()) | ||||||||||||||||
invisible() | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
list( | ||||||||||||||||
capture_plot = capture_plot, | ||||||||||||||||
capture_output = capture_output, | ||||||||||||||||
check_devices = check_devices | ||||||||||||||||
) | ||||||||||||||||
} | ||||||||||||||||
|
||||||||||||||||
read_con <- function(con, buffer = 32 * 1024) { | ||||||||||||||||
|
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious: what does motivate the move for this refactored code after
evaluate_top_level_expression
?All tests are passing so seems good - but wanted to understand the thinking as I would have place it in the same place by precaution
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Over time, my plan is to move to more and more logic in to
evaluate()
so the logic is more clear. In this case specifically, I think it makes sense to check the devices after you've run the user code, rather than before. (Although the net effect is generally going to be the same.)