55.onLoad <- function (... ) {
66 ensureInitialized()
77 .globals $ next_id <- 0L
8- .globals $ global_loop <- create_loop( autorun = FALSE )
9- .globals $ current_loop <- .globals $ global_loop
8+ # Store a ref to the global loop so it doesn't get GC'd.
9+ .globals $ global_loop <- create_loop( parent = NULL )
1010}
1111
1212.globals <- new.env(parent = emptyenv())
13+ # A registry of weak refs to loop handle objects. Given an ID number, we can
14+ # get the corresponding loop handle. We use weak refs because we don't want
15+ # this registry to keep the loop objects alive.
16+ .loops <- new.env(parent = emptyenv())
1317
1418# ' Private event loops
1519# '
4751# '
4852# ' @param loop A handle to an event loop.
4953# ' @param expr An expression to evaluate.
50- # ' @param autorun Should this event loop automatically be run when its parent
51- # ' loop runs? Currently, only FALSE is allowed, but in the future TRUE will
52- # ' be implemented and the default. Because in the future the default will
53- # ' change, for now any code that calls \code{create_loop} must explicitly
54- # ' pass in \code{autorun=FALSE}.
54+ # ' @param autorun This exists only for backward compatibility. If set to
55+ # ' \code{FALSE}, it is equivalent to using \code{parent=NULL}.
56+ # ' @param parent The parent event loop for the one being created. Whenever the
57+ # ' parent loop runs, this loop will also automatically run, without having to
58+ # ' manually call \code{\link{run_now}()} on this loop. If \code{NULL}, then
59+ # ' this loop will not have a parent event loop that automatically runs it; the
60+ # ' only way to run this loop will be by calling \code{\link{run_now}()} on this
61+ # ' loop.
5562# ' @rdname create_loop
5663# '
5764# ' @export
58- create_loop <- function (autorun = NULL ) {
59- if (! identical(autorun , FALSE )) {
60- stop(" autorun must be set to FALSE (until TRUE is implemented)." )
61- }
62-
65+ create_loop <- function (parent = current_loop(), autorun = NULL ) {
6366 id <- .globals $ next_id
6467 .globals $ next_id <- id + 1L
65- createCallbackRegistry(id )
68+
69+ if (! is.null(autorun )) {
70+ # This is for backward compatibility, if `create_loop(autorun=FALSE)` is called.
71+ parent <- NULL
72+ }
73+ if (identical(parent , FALSE )) {
74+ # This is for backward compatibility, if `create_loop(FALSE)` is called.
75+ # (Previously the first and only parameter was `autorun`.)
76+ parent <- NULL
77+ warning(" create_loop(FALSE) is deprecated. Please use create_loop(parent=NULL) from now on." )
78+ }
79+ if (! is.null(parent ) && ! inherits(parent , " event_loop" )) {
80+ stop(" `parent` must be NULL or an event_loop object." )
81+ }
82+
83+ if (is.null(parent )) {
84+ parent_id <- - 1L
85+ } else {
86+ parent_id <- parent $ id
87+ }
88+ createCallbackRegistry(id , parent_id )
6689
6790 # Create the handle for the loop
6891 loop <- new.env(parent = emptyenv())
6992 class(loop ) <- " event_loop"
7093 loop $ id <- id
7194 lockBinding(" id" , loop )
95+
96+ # Add a weak reference to the loop object in our registry.
97+ .loops [[sprintf(" %d" , id )]] <- rlang :: new_weakref(loop )
98+
7299 if (id != 0L ) {
73- # Automatically destroy the loop when the handle is GC'd (unless it's the
74- # global loop.) The global loop handle never gets GC'd under normal
75- # circumstances because .globals$global_loop refers to it. However, if the
76- # package is unloaded it can get GC'd, and we don't want the
77- # destroy_loop() finalizer to give an error message about not being able
78- # to destroy the global loop.
79- reg.finalizer(loop , destroy_loop )
100+ # Inform the C++ layer that there are no more R references when the handle
101+ # is GC'd (unless it's the global loop.) The global loop handle never gets
102+ # GC'd under normal circumstances because .globals$global_loop refers to it.
103+ # However, if the package is unloaded it can get GC'd, and we don't want the
104+ # destroy_loop() finalizer to give an error message about not being able to
105+ # destroy the global loop.
106+ reg.finalizer(loop , notify_r_ref_deleted )
80107 }
81108
82109 loop
83110}
84111
112+ notify_r_ref_deleted <- function (loop ) {
113+ if (identical(loop , global_loop())) {
114+ stop(" Can't notify that reference to global loop is deleted." )
115+ }
116+
117+ res <- notifyRRefDeleted(loop $ id )
118+ if (res ) {
119+ rm(list = sprintf(" %d" , loop $ id ), envir = .loops )
120+ }
121+ invisible (res )
122+ }
123+
85124# ' @rdname create_loop
86125# ' @export
87126destroy_loop <- function (loop ) {
88127 if (identical(loop , global_loop())) {
89128 stop(" Can't destroy global loop." )
90129 }
91130
92- deleteCallbackRegistry(loop $ id )
131+ res <- deleteCallbackRegistry(loop $ id )
132+ if (res ) {
133+ rm(list = sprintf(" %d" , loop $ id ), envir = .loops )
134+ }
135+ invisible (res )
93136}
94137
95138# ' @rdname create_loop
@@ -101,13 +144,24 @@ exists_loop <- function(loop) {
101144# ' @rdname create_loop
102145# ' @export
103146current_loop <- function () {
104- .globals $ current_loop
147+ id <- getCurrentRegistryId()
148+ loop_weakref <- .loops [[sprintf(" %d" , id )]]
149+ if (is.null(loop_weakref )) {
150+ stop(" Current loop with id " , id , " not found." )
151+ }
152+
153+ loop <- rlang :: wref_key(loop_weakref )
154+ if (is.null(loop )) {
155+ stop(" Current loop with id " , id , " not found." )
156+ }
157+
158+ loop
105159}
106160
107161# ' @rdname create_loop
108162# ' @export
109163with_temp_loop <- function (expr ) {
110- loop <- create_loop(autorun = FALSE )
164+ loop <- create_loop(parent = NULL )
111165 on.exit(destroy_loop(loop ))
112166
113167 with_loop(loop , expr )
@@ -116,10 +170,13 @@ with_temp_loop <- function(expr) {
116170# ' @rdname create_loop
117171# ' @export
118172with_loop <- function (loop , expr ) {
119- if (! identical(loop , current_loop())) {
120- old_loop <- .globals $ current_loop
121- on.exit(.globals $ current_loop <- old_loop , add = TRUE )
122- .globals $ current_loop <- loop
173+ if (! exists_loop(loop )) {
174+ stop(" loop has been destroyed!" )
175+ }
176+ old_loop <- current_loop()
177+ if (! identical(loop , old_loop )) {
178+ on.exit(setCurrentRegistryId(old_loop $ id ), add = TRUE )
179+ setCurrentRegistryId(loop $ id )
123180 }
124181
125182 force(expr )
@@ -134,7 +191,11 @@ global_loop <- function() {
134191
135192# ' @export
136193format.event_loop <- function (x , ... ) {
137- paste0(" <event loop>\n id: " , x $ id )
194+ str <- paste0(" <event loop> ID: " , x $ id )
195+ if (! exists_loop(x )) {
196+ str <- paste(str , " (destroyed)" )
197+ }
198+ str
138199}
139200
140201# ' @export
@@ -191,15 +252,17 @@ later <- function(func, delay = 0, loop = current_loop()) {
191252 f <- rlang :: as_function(func )
192253 id <- execLater(f , delay , loop $ id )
193254
194- invisible (create_canceller(id , loop ))
255+ invisible (create_canceller(id , loop $ id ))
195256}
196257
197258# Returns a function that will cancel a callback with the given ID. If the
198259# callback has already been executed or canceled, then the function has no
199260# effect.
200- create_canceller <- function (id , loop ) {
261+ create_canceller <- function (id , loop_id ) {
262+ force(id )
263+ force(loop_id )
201264 function () {
202- invisible (cancel(id , loop $ id ))
265+ invisible (cancel(id , loop_id ))
203266 }
204267}
205268
@@ -236,9 +299,7 @@ run_now <- function(timeoutSecs = 0L, all = TRUE, loop = current_loop()) {
236299 if (! is.numeric(timeoutSecs ))
237300 stop(" timeoutSecs must be numeric" )
238301
239- with_loop(loop ,
240- invisible (execCallbacks(timeoutSecs , all , loop $ id ))
241- )
302+ invisible (execCallbacks(timeoutSecs , all , loop $ id ))
242303}
243304
244305# ' Check if later loop is empty
0 commit comments