mirror of
https://github.com/rstudio/shiny.git
synced 2026-01-09 15:08:04 -05:00
Basic FR infrastructure
Modeled after Meteor deps and session packages
This commit is contained in:
26
react-test.rb
Normal file
26
react-test.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
require './react'
|
||||
|
||||
class Observer
|
||||
def initialize(&proc)
|
||||
@proc = proc
|
||||
run
|
||||
end
|
||||
|
||||
def run
|
||||
ctx = React::Context.new
|
||||
ctx.on_invalidate do
|
||||
run
|
||||
end
|
||||
ctx.run &@proc
|
||||
end
|
||||
end
|
||||
|
||||
sess = React::Session.new
|
||||
user = React::ObservableValue.new { sess.get('user') }
|
||||
upUser = React::ObservableValue.new { (user.get||'').upcase }
|
||||
Observer.new { puts upUser.get }
|
||||
|
||||
sess.set('user', 'jcheng')
|
||||
React::Context.flush
|
||||
sess.set('user', 'jjallaire')
|
||||
React::Context.flush
|
||||
153
react.rb
Normal file
153
react.rb
Normal file
@@ -0,0 +1,153 @@
|
||||
module React
|
||||
|
||||
class Context
|
||||
|
||||
private
|
||||
|
||||
@@next_id = 0
|
||||
@@current_context = nil
|
||||
@@pending_invalidate = []
|
||||
|
||||
public
|
||||
|
||||
attr_reader :callbacks
|
||||
|
||||
def self.current!
|
||||
return current || raise("No current context")
|
||||
end
|
||||
|
||||
def self.current
|
||||
@@current_context
|
||||
end
|
||||
|
||||
attr_reader :id
|
||||
|
||||
def initialize
|
||||
# The ID can used to identify/sort/dedupe contexts
|
||||
@id = @@next_id += 1
|
||||
|
||||
# Indicates whether this context is invalidated, i.e. its
|
||||
# callbacks have been called or are about to be called
|
||||
@invalidated = false
|
||||
|
||||
# List of callbacks to be called after invalidation
|
||||
@callbacks = []
|
||||
end
|
||||
|
||||
# Run a block with this context as the current context. The
|
||||
# original current context will be restored after the block
|
||||
# is executed.
|
||||
def run
|
||||
old_ctx = @@current_context
|
||||
@@current_context = self
|
||||
begin
|
||||
return yield
|
||||
ensure
|
||||
@@current_context = old_ctx
|
||||
end
|
||||
end
|
||||
|
||||
def invalidate
|
||||
return if @invalidated
|
||||
|
||||
@invalidated = true
|
||||
@@pending_invalidate << self
|
||||
end
|
||||
|
||||
# Register a callback to be called after this context is
|
||||
# invalidated (or immediately if it's already invalidated).
|
||||
# The callback takes one argument, the context.
|
||||
def on_invalidate(&callback)
|
||||
if @invalidated
|
||||
callback.call(self)
|
||||
else
|
||||
@callbacks << callback
|
||||
end
|
||||
end
|
||||
|
||||
# Execute all callbacks on invalidated contexts. Will do this
|
||||
# repeatedly if the callbacks themselves cause more invalidations.
|
||||
def self.flush
|
||||
while !@@pending_invalidate.empty?
|
||||
contexts = @@pending_invalidate
|
||||
@@pending_invalidate = []
|
||||
|
||||
contexts.each do |context|
|
||||
context.callbacks.each do |callback|
|
||||
callback.call(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Session
|
||||
def initialize
|
||||
# Key is variable name, value is variable value
|
||||
@values = Hash.new
|
||||
# Key is variable name, value is { Context IDs => Contexts }
|
||||
@dependencies = Hash.new
|
||||
end
|
||||
|
||||
def get(name)
|
||||
cur_ctx = React::Context.current!
|
||||
@dependencies[name] = @dependencies[name] || Hash.new
|
||||
if !@dependencies[name].has_key?(cur_ctx.id)
|
||||
@dependencies[name][cur_ctx.id] = cur_ctx
|
||||
cur_ctx.on_invalidate do
|
||||
@dependencies[name].delete(cur_ctx.id)
|
||||
end
|
||||
end
|
||||
|
||||
return @values[name]
|
||||
end
|
||||
|
||||
def set(name, value)
|
||||
if @values.has_key?(name) && @values[name] == value
|
||||
return
|
||||
end
|
||||
|
||||
@values[name] = value
|
||||
if @dependencies[name]
|
||||
@dependencies[name].each_value {|ctx| ctx.invalidate}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class ObservableValue
|
||||
def initialize(&valueProc)
|
||||
@valueProc = valueProc
|
||||
|
||||
@dependencies = Hash.new
|
||||
|
||||
update_value
|
||||
end
|
||||
|
||||
def get
|
||||
cur_ctx = React::Context.current!
|
||||
@dependencies[cur_ctx.id] = cur_ctx
|
||||
cur_ctx.on_invalidate do
|
||||
@dependencies.delete cur_ctx.id
|
||||
end
|
||||
@value
|
||||
end
|
||||
|
||||
private
|
||||
def update_value
|
||||
old_value = @value
|
||||
|
||||
ctx = Context.new
|
||||
ctx.on_invalidate do
|
||||
update_value
|
||||
end
|
||||
ctx.run do
|
||||
@value = @valueProc.call
|
||||
end
|
||||
|
||||
if old_value != @value
|
||||
@dependencies.each_value {|dep_ctx| dep_ctx.invalidate}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
Reference in New Issue
Block a user