# # reacruby - Reactive Programming on Ruby # # (c) 2008, yhara@kmc.gr.jp require 'curses' #class Fixnum # alias __old_plus__ + # def +(other) # if other.class.to_s =~ /Reac/ #:-( # Reac(self) + other # else # self.__old_plus__(other) # end # end #end class String alias __old_plus__ + def +(other) if other.kind_of?(Reac) Reac(self) + other.to_s else #p raise(other.inspect) self.__old_plus__(other) end end end class Reac attr_accessor :last_update # construct tree of Reac::* # (instead of call the method) # to call it later def method_missing(method, *args) Call.new(self, method, args) end # caluculate the value # (short-hand for Reac.value(x)) def value Reac.value(self) end # caluculate the value by traversing the tree def self.value(reac, tick=nil) return reac if not reac.is_a?(Reac) # return cached data if already calculated return reac.data if tick && (reac.last_update == tick) reac.last_update = tick case reac when Value reac.data when Call receiver = Reac.value(reac.receiver, tick) args = reac.args.map{|item| Reac.value(item, tick) } reac.data = receiver.__send__(reac.method, *args) when Proc reac.data = reac.proc.call when Array reac.data = reac.ary.map{|item| Reac.value(item, tick)} else raise "must not happen" end end class Value < Reac def initialize(data) @data = data end attr_reader :data undef :to_s end class Call < Reac def initialize(receiver, method, args) @receiver, @method, @args = receiver, method, args end attr_accessor :data attr_reader :receiver, :method, :args undef :to_s end class Proc < Reac def initialize(proc) @proc = proc end attr_accessor :data attr_reader :proc undef :to_s end class Array < Reac def initialize(ary) @ary = ary end attr_accessor :data attr_reader :ary undef :to_s undef :inspect end end # construct Reac value from normal value (or proc) def Reac(obj=nil, &block) if block Reac::Proc.new(block) else if obj.is_a?(Array) Reac::Array.new(obj) else Reac::Value.new(obj) end end end class Reac class CursesView def initialize @top_window = Curses.init_screen @x = @y = 0 #Curses.noecho #Curses.nocbreak # disable buffering keyboard input Curses.clear Curses.refresh @reacs = [] #@top_window = Curses::Window.new(Curses.cols-2, Curses.lines-1, 0, 0) #y, x = 0 end def put(y, x, reac) @reacs << [y, x, reac] end def start(secs=nil) main_loop = proc{ tick = Time.now.to_i @top_window.clear @reacs.each do |y, x, reac| @top_window.setpos(y, x) @top_window << Reac.value(reac, tick).to_s @top_window.refresh end sleep 1.0 } if secs secs.times{ main_loop.call } else loop{ main_loop.call } end end end end # class Reac # class View # end # # class CursesView < View # end # # class SDLView < View # end # # end x = Reac{rand(100)} y = x * 2 t = Reac{Time.now} view = Reac::CursesView.new view.put(0, 0, "x : " + x) view.put(1, 0, "y : " + y) view.put(2, 0, "x and y : " + Reac([x, y]).inspect) view.put(4, 0, "now : " + t.strftime("%H:%M:%S")) view.put(5, 0, "second * 100 : " + t.sec * 100) view.start(5)