mirror of
https://github.com/rstudio/shiny.git
synced 2026-02-02 02:34:57 -05:00
- Use websockets package to implement Shiny server in R - NB: Current behavior is undefined if more than one client connects at the same time - Added HTML and plot (actually image) binding types on the client
304 lines
7.0 KiB
R
304 lines
7.0 KiB
R
# TESTS
|
|
# Simple set/get
|
|
# Simple remove
|
|
# Simple contains.key
|
|
# Simple keys
|
|
# Simple values
|
|
# Simple clear
|
|
# Get of unknown key returns NULL
|
|
# Remove of unknown key does nothing
|
|
# Setting a key twice always results in last-one-wins
|
|
# /TESTS
|
|
Map <- setRefClass(
|
|
'Map',
|
|
fields = list(
|
|
.env = 'environment'
|
|
),
|
|
methods = list(
|
|
initialize = function() {
|
|
.env <<- new.env(parent=emptyenv())
|
|
},
|
|
get = function(key) {
|
|
if (.self$contains.key(key))
|
|
return(base::get(key, pos=.env, inherits=F))
|
|
else
|
|
return(NULL)
|
|
},
|
|
set = function(key, value) {
|
|
assign(key, value, pos=.env, inherits=F)
|
|
return(value)
|
|
},
|
|
remove = function(key) {
|
|
if (.self$contains.key(key)) {
|
|
result <- .self$get(key)
|
|
rm(list = key, pos=.env, inherits=F)
|
|
return(result)
|
|
}
|
|
return(NULL)
|
|
},
|
|
contains.key = function(key) {
|
|
exists(key, where=.env, inherits=F)
|
|
},
|
|
keys = function() {
|
|
ls(envir=.env, all.names=T)
|
|
},
|
|
values = function() {
|
|
mget(.self$keys(), envir=.env, inherits=F)
|
|
},
|
|
clear = function() {
|
|
.env <<- new.env(parent=emptyenv())
|
|
invisible(NULL)
|
|
},
|
|
size = function() {
|
|
length(.env)
|
|
}
|
|
)
|
|
)
|
|
|
|
as.list.Map <- function(map) {
|
|
sapply(map$keys(),
|
|
map$get,
|
|
simplify=F)
|
|
}
|
|
length.Map <- function(map) {
|
|
map$size()
|
|
}
|
|
|
|
Context <- setRefClass(
|
|
'Context',
|
|
fields = list(
|
|
id = 'character',
|
|
.invalidated = 'logical',
|
|
.callbacks = 'list'
|
|
),
|
|
methods = list(
|
|
initialize = function() {
|
|
id <<- .get.reactive.environment()$next.id()
|
|
.invalidated <<- F
|
|
.callbacks <<- list()
|
|
},
|
|
run = function(func) {
|
|
env <- .get.reactive.environment()
|
|
old.ctx <- env$current.context(warn=F)
|
|
env$set.current.context(.self)
|
|
on.exit(env$set.current.context(old.ctx))
|
|
func()
|
|
},
|
|
invalidate = function() {
|
|
if (.invalidated)
|
|
return()
|
|
.invalidated <<- T
|
|
.get.reactive.environment()$add.pending.invalidate(.self)
|
|
NULL
|
|
},
|
|
on.invalidate = function(func) {
|
|
if (.invalidated)
|
|
func()
|
|
else
|
|
.callbacks <<- c(.callbacks, func)
|
|
NULL
|
|
},
|
|
execute.callbacks = function() {
|
|
lapply(.callbacks, function(func) {
|
|
func()
|
|
})
|
|
}
|
|
)
|
|
)
|
|
|
|
ReactiveEnvironment <- setRefClass(
|
|
'ReactiveEnvironment',
|
|
fields = c('.current.context', '.next.id', '.pending.invalidate'),
|
|
methods = list(
|
|
initialize = function() {
|
|
.current.context <<- NULL
|
|
.next.id <<- 0L
|
|
.pending.invalidate <<- list()
|
|
},
|
|
next.id = function() {
|
|
.next.id <<- .next.id + 1L
|
|
return(as.character(.next.id))
|
|
},
|
|
current.context = function(warn=T) {
|
|
if (warn && is.null(.current.context))
|
|
warning('No reactive context is active')
|
|
return(.current.context)
|
|
},
|
|
set.current.context = function(ctx) {
|
|
.current.context <<- ctx
|
|
},
|
|
add.pending.invalidate = function(ctx) {
|
|
.pending.invalidate <<- c(.pending.invalidate, ctx)
|
|
},
|
|
flush = function() {
|
|
while (length(.pending.invalidate) > 0) {
|
|
contexts <- .pending.invalidate
|
|
.pending.invalidate <<- list()
|
|
lapply(contexts, function(ctx) {
|
|
ctx$execute.callbacks()
|
|
NULL
|
|
})
|
|
}
|
|
}
|
|
)
|
|
)
|
|
|
|
Values <- setRefClass(
|
|
'Values',
|
|
fields = list(
|
|
.values = 'environment',
|
|
.dependencies = 'environment'
|
|
),
|
|
methods = list(
|
|
initialize = function() {
|
|
.values <<- new.env(parent=emptyenv())
|
|
.dependencies <<- new.env(parent=emptyenv())
|
|
},
|
|
get = function(key) {
|
|
ctx <- .get.reactive.environment()$current.context()
|
|
dep.key <- paste(key, ':', ctx$id, sep='')
|
|
if (!exists(dep.key, where=.dependencies, inherits=F)) {
|
|
assign(dep.key, ctx, pos=.dependencies, inherits=F)
|
|
ctx$on.invalidate(function() {
|
|
rm(list=dep.key, pos=.dependencies, inherits=F)
|
|
})
|
|
}
|
|
|
|
if (!exists(key, where=.values, inherits=F))
|
|
NULL
|
|
else
|
|
base::get(key, pos=.values, inherits=F)
|
|
},
|
|
set = function(key, value) {
|
|
if (exists(key, where=.values, inherits=F)) {
|
|
if (identical(base::get(key, pos=.values, inherits=F), value)) {
|
|
return(invisible())
|
|
}
|
|
}
|
|
|
|
assign(key, value, pos=.values, inherits=F)
|
|
dep.keys <- objects(
|
|
pos=.dependencies,
|
|
pattern=paste('^\\Q', key, ':', '\\E', '\\d+$', sep='')
|
|
)
|
|
lapply(
|
|
mget(dep.keys, envir=.dependencies),
|
|
function(ctx) {
|
|
ctx$invalidate()
|
|
NULL
|
|
}
|
|
)
|
|
invisible()
|
|
},
|
|
mset = function(lst) {
|
|
lapply(names(lst),
|
|
function(name) {
|
|
.self$set(name, lst[[name]])
|
|
})
|
|
}
|
|
)
|
|
)
|
|
|
|
Observable <- setRefClass(
|
|
'Observable',
|
|
fields = c(
|
|
'.func', # function
|
|
'.dependencies', # Map
|
|
'.initialized', # logical
|
|
'.value' # any
|
|
),
|
|
methods = list(
|
|
initialize = function(func) {
|
|
.func <<- func
|
|
.dependencies <<- Map$new()
|
|
.initialized <<- F
|
|
},
|
|
get.value = function() {
|
|
if (!.initialized) {
|
|
.initialized <<- T
|
|
.self$.update.value()
|
|
}
|
|
|
|
ctx <- .get.reactive.environment()$current.context()
|
|
if (!.dependencies$contains.key(ctx$id)) {
|
|
.dependencies$set(ctx$id, ctx)
|
|
ctx$on.invalidate(function() {
|
|
.dependencies$remove(ctx$id)
|
|
})
|
|
}
|
|
return(.value)
|
|
},
|
|
.update.value = function() {
|
|
old.value <- .value
|
|
|
|
ctx <- Context$new()
|
|
ctx$on.invalidate(function() {
|
|
.self$.update.value()
|
|
})
|
|
ctx$run(function() {
|
|
.value <<- .func()
|
|
})
|
|
if (!identical(old.value, .value)) {
|
|
lapply(
|
|
.dependencies$values(),
|
|
function(dep.ctx) {
|
|
dep.ctx$invalidate()
|
|
NULL
|
|
}
|
|
)
|
|
}
|
|
}
|
|
)
|
|
)
|
|
|
|
Observer <- setRefClass(
|
|
'Observer',
|
|
fields = list(
|
|
.func = 'function'
|
|
),
|
|
methods = list(
|
|
initialize = function(func) {
|
|
.func <<- func
|
|
.self$run()
|
|
},
|
|
run = function() {
|
|
ctx <- Context$new()
|
|
ctx$on.invalidate(function() {
|
|
run()
|
|
})
|
|
ctx$run(.func)
|
|
}
|
|
)
|
|
)
|
|
|
|
.get.reactive.environment <- function() {
|
|
if (!exists('.ReactiveEnvironment', envir=.GlobalEnv, inherits=F)) {
|
|
assign('.ReactiveEnvironment', ReactiveEnvironment$new(), envir=.GlobalEnv)
|
|
}
|
|
get('.ReactiveEnvironment', envir=.GlobalEnv, inherits=F)
|
|
}
|
|
|
|
flush.react <- function() {
|
|
.get.reactive.environment()$flush()
|
|
}
|
|
|
|
test <- function () {
|
|
values <- Values$new()
|
|
obs <- Observer$new(function() {print(values$get('foo'))})
|
|
flush.react()
|
|
values$set('foo', 'bar')
|
|
flush.react()
|
|
|
|
values$set('a', 100)
|
|
values$set('b', 250)
|
|
observable <- Observable$new(function() {
|
|
values$get('a') + values$get('b')
|
|
})
|
|
obs2 <- Observer$new(function() {print(paste0('a+b: ', observable$get.value()))})
|
|
flush.react()
|
|
values$set('b', 300)
|
|
flush.react()
|
|
values$mset(list(a = 10, b = 20))
|
|
flush.react()
|
|
}
|