Conversation
137f0d9 to
34baa70
Compare
34baa70 to
845b4a7
Compare
| // Activate the graphics device | ||
| frontend.send_execute_request("plot(1:10)", ExecuteRequestOptions::default()); | ||
| frontend.recv_iopub_busy(); | ||
| frontend.recv_iopub_execute_input(); | ||
| frontend.recv_iopub_display_data(); | ||
| frontend.recv_iopub_idle(); | ||
| frontend.recv_shell_execute_reply(); |
There was a problem hiding this comment.
This smells a bit funny. I don't think you should need this if things are working correctly
| dap: Arc<Mutex<Dap>>, | ||
| session_mode: SessionMode, | ||
| ) -> Self { | ||
| let device_context = DeviceContext::new(iopub_tx.clone()); |
There was a problem hiding this comment.
It makes sense to me to remove iopub_tx from DeviceContext now and only pull iopub_tx directly from Console
Over in graphics_device.rs, you can just Console::get().iopub_tx() anytime you need it now!
It feels like this better scopes responsibilities to me
There was a problem hiding this comment.
I feel the opposite. It hides that the device context directly communicates with the frontend via IOPub.
Also ideally we should avoid Console::get() calls as these bypass Rust's ownership model. But then the issue is that the device belongs to console and not the other way around. Cloning the IOPub channel is the right ownership structure from that point of view.
| Console::get() | ||
| .device_context() | ||
| .set_prerender_settings(params.settings); |
There was a problem hiding this comment.
Is it possible that we could now make DeviceContext have non-Cell internals?
I am disturbed by being able to call get() to then perform a mutable update, rather than being forced to go through get_mut()
| // Don't try to render a plot if we're currently drawing. | ||
| if self.is_drawing.get() { | ||
| log::trace!("Refusing to render due to `is_drawing`"); | ||
| return; | ||
| } | ||
|
|
||
| // Don't try to render a plot if someone is asking us not to, i.e. `dev.hold()` | ||
| if !self.should_render.get() { | ||
| log::trace!("Refusing to render due to `should_render`"); | ||
| return; | ||
| } |
There was a problem hiding this comment.
I am pretty sure these are important
| return; | ||
| } | ||
|
|
||
| // Collect existing sockets into a vector of tuples. |
There was a problem hiding this comment.
Broken regression from this PR
plt2 = function() {
layout(matrix(1:2, 2))
plot(1, 1)
plot(1, 1)
}
plt2()There was a problem hiding this comment.
In general, just send this whole chunk "all at once" rather than in separate requests
layout(matrix(1:2, 2))
plot(1, 1)
plot(1, 1)| // Collect existing sockets into a vector of tuples. | ||
| // Necessary for handling Select in a clean way. | ||
| let sockets = { | ||
| // Refcell Safety: Clone the hashmap so we don't hold a reference for too long |
There was a problem hiding this comment.
Broken regression
# Stars raster
library(stars)
library(terra)
r <- matrix(runif(9), nrow = 3) |>
st_as_stars()
plot(r)
rast(r) |> plot()| let sockets = { | ||
| // Refcell Safety: Clone the hashmap so we don't hold a reference for too long | ||
| let sockets = self.sockets.borrow().clone(); | ||
| sockets.into_iter().collect::<Vec<_>>() |
There was a problem hiding this comment.
Broken regression
# rpart
library(rpart)
library(rpart.plot)
model <- rpart(Species ~ ., data = iris, method = "class")
rpart.plot(model)There was a problem hiding this comment.
Actually something somehow seems deeply broken by this? I am getting blank new-pages fairly often and reproducibly. Run these 3 plots one at a time.
# rpart
library(rpart)
library(rpart.plot)
model <- rpart(Species ~ ., data = iris, method = "class")
rpart.plot(model)
# sf
geo <- sf::st_make_grid(
cellsize = c(1, 1),
offset = c(0, 0),
n = 10
)
x <- sample(c(0, 1), 100, prob = c(0.9, 0.1), replace = TRUE)
plot(sf::st_sf(x = x, geo))
# rpart again
library(rpart)
library(rpart.plot)
model <- rpart(Species ~ ., data = iris, method = "class")
rpart.plot(model)Screen.Recording.2026-03-16.at.4.34.47.PM.mov
| let sockets = self.sockets.borrow().clone(); | ||
| sockets.into_iter().collect::<Vec<_>>() | ||
| }; | ||
|
|
There was a problem hiding this comment.
In general here is my "regression script" I run through one at a time (following the per example instructions of whether to run it one line at a time or as a chunk of code)
All of these should work
# -------------------------
# Multiple plots at once
# Run the chunk all at once
plot(1:10)
plot(2:20)
# Run the chunk all at once
library(ggplot2)
g <- ggplot(mpg, aes(class))
# Number of cars in each class:
g + geom_bar()
# Total engine displacement of each class
g + geom_bar(aes(weight = displ))
# Map class to y instead to flip the orientation
ggplot(mpg) + geom_bar(aes(y = class))
# Inside a function
plt2 = function() {
layout(matrix(1:2, 2))
plot(1, 1)
plot(1, 1)
}
plt2()
# Multiple ggplots
library(ggplot2)
library(purrr)
economics_list <- split(economics_long, economics_long$variable)
economics_list |>
imap(\(df, nm) {
df |>
ggplot(aes(date, value)) +
geom_line() +
labs(title = nm)
})
library(ggExtra)
library(ggplot2)
set.seed(30)
df <- data.frame(x = rnorm(500, 50, 10), y = runif(500, 0, 50))
p2 <- ggplot(df, aes(x, y)) + geom_point()
ggMarginal(p2, type = "boxplot")
ggMarginal(p2, type = "histogram")
par(mfrow = c(1, 1))
# -------------------------
# Plotting in a loop
# (Note it would be nice if we could process renders at interrupt time)
for (i in 1:5) {
plot(i)
Sys.sleep(1)
}
# -------------------------
# Par related examples
par(mfrow = c(2, 2))
hist(
iris$Petal.Width[iris$Species == "virginica"],
xlim = c(0, 3),
breaks = 9,
main = "Petal Width for Virginica",
xlab = "",
col = "green"
)
hist(
iris$Petal.Width[iris$Species == "versicolor"],
xlim = c(0, 3),
breaks = 9,
main = "Petal Width for Versicolor",
xlab = "",
col = "blue"
)
hist(
iris$Petal.Width[iris$Species == "versicolor"],
xlim = c(0, 3),
breaks = 9,
main = "Petal Width for Versicolor",
xlab = "",
col = "blue"
)
plot(1:10)
par(mfrow = c(1, 1))
par(mfrow = c(2, 1))
plot(rnorm(100))
plot(rnorm(1000))
par(mfrow = c(1, 1))
# Stars raster
library(stars)
library(terra)
r <- matrix(runif(9), nrow = 3) |>
st_as_stars()
plot(r)
rast(r) |> plot()
# Performance models
library(performance)
data(mtcars)
m <- lm(mpg ~ gear + cyl + hp, data = mtcars)
check_model(m)
m <- lm(mpg ~ gear + cyl + hp, data = mtcars)
p <- plot(check_model(m), panel = FALSE)
p[[1]] # works
p[[2]] # works
# blank plot
patchwork::wrap_plots(p)
# rpart
library(rpart)
library(rpart.plot)
model <- rpart(Species ~ ., data = iris, method = "class")
rpart.plot(model)
# layout() related
# layout(matrix(
# c(1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2),
# nrow = 4,
# byrow = TRUE
# ))
# layout.show(n = 2)
# sf
geo <- sf::st_make_grid(
cellsize = c(1, 1),
offset = c(0, 0),
n = 10
)
x <- sample(c(0, 1), 100, prob = c(0.9, 0.1), replace = TRUE)
plot(sf::st_sf(x = x, geo))
# biplot
dat <- mtcars
pr <- princomp(dat)
biplot(pr)
# -------------------------
# dev.hold()-ing
# Run these one at a time!
# (RStudio can't actually do this)
dev.hold()
plot(1:10)
abline(1, 2)
dev.flush()
# -------------------------
# tinyplots
library(tinyplot)
plt(Sepal.Length ~ Petal.Length, data = iris)
plt(Sepal.Length ~ Petal.Length | Species, data = iris)
plt(Sepal.Length ~ Petal.Length | Species, data = iris, legend = "bottomright")
# generate data
lambda1 <- 1.8
lambda2 <- 0.7
x <- 0:10
p1 <- dpois(x, lambda1)
p2 <- dpois(x, lambda2)
dp <- data.frame(
x = c(x, x),
p = c(p1, p2),
lambda = rep(c(lambda1, lambda2), each = length(x))
)
dp$lambda <- as.factor(dp$lambda)
# plot
plt(
p ~ x,
facet = ~lambda,
data = dp,
type = "h",
lwd = 2,
col = "skyblue",
draw = c(
axis(1, at = x, labels = x),
points(p ~ x, data = dp, col = "black")
)
)
# -------------------------
# The dreaded graphics device swap
# Run this all at once
library(ggplot2)
p <- ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point()
p
ggsave("temp.png", p)
# Run this all at once
plot(1:10)
grDevices::png()
plot(1:20)
dev.off()
# -------------------------
# Something related to big plots was not working when you resize
# (I did some perf improvements)
library(qgraph)
data(big5)
data(big5groups)
# First plot
qgraph(
cor(big5),
minimum = 0.25,
groups = big5groups,
legend = TRUE,
borders = FALSE,
title = "Big 5 correlations"
)
# Second identical plot
qgraph(
cor(big5),
minimum = 0.25,
groups = big5groups,
legend = TRUE,
borders = FALSE,
title = "Big 5 correlations"
)
# -------------------------
# Doesn't work - stepping through plots one at a time
# Not precisely sure why, because in theory we are at idle time
fit_lm <- lm(
mpg ~ cyl + wt,
data = mtcars
)
# Renders only plot(fit_lm, which = 5)
# Not able to scroll through the which = 1, 2, 3 plots
# that are automatically called by plot.lm().
plot(fit_lm)
# -------------------------
# Doesn't work - we don't respond to render requests in the middle of R execution
# We respond at idle time right now in `process_events()`.
# So all the plots appear at once at the end
tourr::animate_xy(tourr::flea[, 1:6])
# Simpler example of the idea
{
plot(1:10)
plot(2:11)
Sys.sleep(5)
}
There was a problem hiding this comment.
Thanks! I'll add these as test cases
bd54059 to
be02717
Compare
Progress towards #689
Branched from #1099
I did not foresee all the simplifications falling out of this one!
Plots no longer live in their own threads, they live on the R thread. That frees up memory since each background thread takes about 2mb of stack space (depending on platform), so 20 open plots would take 40mb of memory.
DeviceContextmoves from a standalonethread_local!into a regular field ofConsole. All access goes throughConsole::get().device_context(). TheCommHandlerContextprovides access toConsoleviactx.console(), so plot handlers reach the device context through a controlled context chain.The old idle-time polling loop (
process_rpc_requestswithcrossbeam::Select) is removed. Plot RPCs are now dispatched synchronously through the shell handler on the R thread. This improves plot latency when pre-renderings require adjustments because we no longer timeout-poll for plot updates (related to Performance for plotting positron#5184).The
GraphicsDeviceNotificationasync channel is removed. Since the UI comm now runs on the R thread (from Run UI comm on the R thread #1099), prerender settings are updated synchronously viaConsole::get().device_context().set_prerender_settings(). This removes quite a bit of plumbing.Fixes a
dev.hold()regression I introduced with pre-renderings (Send pre-renderings of new plots to the frontend #775). We were previously sending pre-renderings unconditionally, now they are held untildev.flush(). This is tested by new integration tests.