reac 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +2 -0
- data/lib/reac/reactivize.rb +69 -0
- data/lib/reac/view/curses.rb +33 -0
- data/lib/reac/view/sdl.rb +92 -0
- data/lib/reac/view.rb +26 -0
- data/lib/reac.rb +96 -0
- data/sample/boxfont2.ttf +0 -0
- data/sample/clock.rb +24 -0
- data/sample/icon.bmp +0 -0
- data/sample/reacirb.rb +103 -0
- data/sample/sample.rb +29 -0
- data/sample/sdltest.rb +12 -0
- metadata +64 -0
data/README.rdoc
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
#
|
2
|
+
# Reac::Reactivize
|
3
|
+
#
|
4
|
+
class Reac
|
5
|
+
module Reactivize
|
6
|
+
# reactivize - make methods to take reactive values
|
7
|
+
# NOTE: currently supports one-arg method only
|
8
|
+
#
|
9
|
+
# exmaple:
|
10
|
+
#class String
|
11
|
+
# alias __old_plus__ +
|
12
|
+
# def +(other)
|
13
|
+
# if other.kind_of?(Reac)
|
14
|
+
# Reac(self) + other
|
15
|
+
# else
|
16
|
+
# self.__old_plus__(other)
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#end
|
20
|
+
OLD_NAME = {
|
21
|
+
:+ => "plus",
|
22
|
+
:- => "minus",
|
23
|
+
:* => "mul",
|
24
|
+
:/ => "div"
|
25
|
+
}
|
26
|
+
def reactivize(*methods)
|
27
|
+
methods.each do |m|
|
28
|
+
old_name = OLD_NAME[m] || m
|
29
|
+
module_eval <<-EOD
|
30
|
+
alias __old_#{old_name}__ #{m}
|
31
|
+
def #{m}(arg)
|
32
|
+
if arg.kind_of?(Reac)
|
33
|
+
Reac(self).#{m}(arg)
|
34
|
+
else
|
35
|
+
__old_#{old_name}__(arg)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
EOD
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# reactivize singleton methods.
|
43
|
+
#
|
44
|
+
# example:
|
45
|
+
# module Math
|
46
|
+
# reactivize_singleton :sin, :cos
|
47
|
+
# end
|
48
|
+
def reactivize_singleton(*methods)
|
49
|
+
method_list = methods.map{|m|m.inspect}.join(',')
|
50
|
+
instance_eval <<-EOD
|
51
|
+
class << self
|
52
|
+
reactivize #{method_list}
|
53
|
+
end
|
54
|
+
EOD
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# now you can use 'reactivize' in all classes & modules
|
60
|
+
Module.__send__(:include, Reac::Reactivize)
|
61
|
+
|
62
|
+
class String
|
63
|
+
reactivize :+, :*
|
64
|
+
end
|
65
|
+
|
66
|
+
class Fixnum
|
67
|
+
reactivize :+, :-, :*, :/
|
68
|
+
end
|
69
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'curses'
|
2
|
+
|
3
|
+
class Reac
|
4
|
+
class CursesView < View
|
5
|
+
def initialize(&block)
|
6
|
+
@top_window = Curses.init_screen
|
7
|
+
@x = @y = 0
|
8
|
+
#Curses.noecho
|
9
|
+
#Curses.nocbreak # disable buffering keyboard input
|
10
|
+
Curses.clear
|
11
|
+
Curses.refresh
|
12
|
+
@reacs = []
|
13
|
+
#@top_window = Curses::Window.new(Curses.cols-2, Curses.lines-1, 0, 0) #y, x = 0
|
14
|
+
|
15
|
+
instance_eval(&block) if block
|
16
|
+
end
|
17
|
+
|
18
|
+
def put(y, x, reac)
|
19
|
+
@reacs << [y, x, reac]
|
20
|
+
end
|
21
|
+
|
22
|
+
def main_loop(now)
|
23
|
+
@top_window.clear
|
24
|
+
@reacs.each do |y, x, reac|
|
25
|
+
yval = Reac.value(y, now)
|
26
|
+
xval = Reac.value(x, now)
|
27
|
+
@top_window.setpos(yval, xval)
|
28
|
+
@top_window << Reac.value(reac, now).to_s
|
29
|
+
@top_window.refresh
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'sdl'
|
2
|
+
|
3
|
+
class Reac
|
4
|
+
|
5
|
+
class SDLView < View
|
6
|
+
|
7
|
+
def self.init(w=640, h=480, options={})
|
8
|
+
@@width, @@height = w, h
|
9
|
+
bpp = options[:bpp] || 32
|
10
|
+
flags = options[:flags] || SDL::SWSURFACE
|
11
|
+
|
12
|
+
SDL.init(options[:init_flags] || SDL::INIT_EVERYTHING)
|
13
|
+
SDL::TTF.init unless options[:no_ttf]
|
14
|
+
@@screen = SDL.set_video_mode(w, h, bpp, flags)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(options={}, &block)
|
18
|
+
@reacs = {
|
19
|
+
:images => [],
|
20
|
+
:bmfonts => [],
|
21
|
+
:ttffonts => [],
|
22
|
+
}
|
23
|
+
@background_color = options[:background] || [0, 0, 0]
|
24
|
+
|
25
|
+
instance_eval(&block) if block
|
26
|
+
end
|
27
|
+
|
28
|
+
def put(_x, _y, _obj, *rest)
|
29
|
+
case _obj
|
30
|
+
when SDL::Surface
|
31
|
+
put_image(_x, _y, _obj)
|
32
|
+
else
|
33
|
+
put_string(_x, _y, _obj, *rest)
|
34
|
+
#raise ArgumentError, "unkown object type: #{_obj.class}"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def main_loop(now)
|
39
|
+
@@screen.fill_rect(0, 0, @@width, @@height, @background_color)
|
40
|
+
@reacs[:images].each do |_x, _y, _image|
|
41
|
+
x = Reac.value(_x, now)
|
42
|
+
y = Reac.value(_y, now)
|
43
|
+
image = Reac.value(_image, now)
|
44
|
+
@@screen.put(image, x, y)
|
45
|
+
end
|
46
|
+
@reacs[:bmfonts].each do |_x, _y, _str, font|
|
47
|
+
x = Reac.value(_x, now)
|
48
|
+
y = Reac.value(_y, now)
|
49
|
+
str = Reac.value(_str, now)
|
50
|
+
i = 0
|
51
|
+
str.each_line do |line|
|
52
|
+
font.textout(@@screen, line, x, y + font.height*i)
|
53
|
+
i += 1
|
54
|
+
end
|
55
|
+
end
|
56
|
+
col = [255,255,255]
|
57
|
+
@reacs[:ttffonts].each do |_x, _y, _str, font|
|
58
|
+
x = Reac.value(_x, now)
|
59
|
+
y = Reac.value(_y, now)
|
60
|
+
str = Reac.value(_str, now)
|
61
|
+
i = 0
|
62
|
+
str.each_line do |line|
|
63
|
+
font.draw_solid_utf8(@@screen, line, x, y + font.line_skip*i, *col)
|
64
|
+
i += 1
|
65
|
+
end
|
66
|
+
end
|
67
|
+
@@screen.flip
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
#TODO: rewrite in OOP style (use inheritance)
|
73
|
+
def put_image(_x, _y, _image)
|
74
|
+
@reacs[:images] << [_x, _y, _image]
|
75
|
+
end
|
76
|
+
|
77
|
+
def put_string(_x, _y, _str, font)
|
78
|
+
case font
|
79
|
+
# when SDL::BMFont
|
80
|
+
# @reacs[:bmfonts] << [_x, _y, _str, font]
|
81
|
+
when SDL::TTF
|
82
|
+
@reacs[:ttffonts] << [_x, _y, _str, font]
|
83
|
+
when SDL::Kanji
|
84
|
+
raise NotImplementedError, "this font type is not yet supported.."
|
85
|
+
else
|
86
|
+
raise ArgumentError, "unkown font type: #{font.class}"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
data/lib/reac/view.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
class Reac
|
2
|
+
|
3
|
+
class View
|
4
|
+
|
5
|
+
WAIT = 0.1
|
6
|
+
|
7
|
+
def start(secs=nil)
|
8
|
+
if secs
|
9
|
+
start = Time.now
|
10
|
+
loop{
|
11
|
+
now = Time.now
|
12
|
+
main_loop(now)
|
13
|
+
break if now - start > secs
|
14
|
+
sleep WAIT
|
15
|
+
}
|
16
|
+
else
|
17
|
+
loop{
|
18
|
+
main_loop(Time.now)
|
19
|
+
sleep WAIT
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
data/lib/reac.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
#
|
2
|
+
# reacruby - Reactive Programming on Ruby
|
3
|
+
#
|
4
|
+
# (c) 2008, yhara@kmc.gr.jp
|
5
|
+
require 'reac/reactivize.rb'
|
6
|
+
require 'reac/view.rb'
|
7
|
+
|
8
|
+
class Reac
|
9
|
+
attr_accessor :last_update
|
10
|
+
|
11
|
+
# construct tree of Reac::*
|
12
|
+
# (instead of call the method)
|
13
|
+
# to call it later
|
14
|
+
def method_missing(method, *args)
|
15
|
+
Call.new(self, method, args)
|
16
|
+
end
|
17
|
+
|
18
|
+
# caluculate the value
|
19
|
+
# (short-hand for Reac.value(x))
|
20
|
+
def value
|
21
|
+
Reac.value(self)
|
22
|
+
end
|
23
|
+
|
24
|
+
# caluculate the value by traversing the tree
|
25
|
+
def self.value(reac, tick=nil)
|
26
|
+
return reac if not reac.is_a?(Reac)
|
27
|
+
|
28
|
+
# return cached data if already calculated
|
29
|
+
return reac.data if tick && (reac.last_update == tick)
|
30
|
+
|
31
|
+
reac.last_update = tick
|
32
|
+
case reac
|
33
|
+
when Value
|
34
|
+
reac.data
|
35
|
+
when Call
|
36
|
+
receiver = Reac.value(reac.receiver, tick)
|
37
|
+
args = reac.args.map{|item|
|
38
|
+
Reac.value(item, tick)
|
39
|
+
}
|
40
|
+
reac.data = receiver.__send__(reac.method, *args)
|
41
|
+
when Proc
|
42
|
+
reac.data = reac.proc.call
|
43
|
+
when Array
|
44
|
+
reac.data = reac.ary.map{|item| Reac.value(item, tick)}
|
45
|
+
else
|
46
|
+
raise "must not happen"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
undef :to_s
|
50
|
+
undef :inspect
|
51
|
+
undef :==
|
52
|
+
|
53
|
+
class Value < Reac
|
54
|
+
def initialize(data)
|
55
|
+
@data = data
|
56
|
+
end
|
57
|
+
attr_reader :data
|
58
|
+
end
|
59
|
+
|
60
|
+
class Call < Reac
|
61
|
+
def initialize(receiver, method, args)
|
62
|
+
@receiver, @method, @args = receiver, method, args
|
63
|
+
end
|
64
|
+
attr_accessor :data
|
65
|
+
attr_reader :receiver, :method, :args
|
66
|
+
end
|
67
|
+
|
68
|
+
class Proc < Reac
|
69
|
+
def initialize(proc)
|
70
|
+
@proc = proc
|
71
|
+
end
|
72
|
+
attr_accessor :data
|
73
|
+
attr_reader :proc
|
74
|
+
end
|
75
|
+
|
76
|
+
class Array < Reac
|
77
|
+
def initialize(ary)
|
78
|
+
@ary = ary
|
79
|
+
end
|
80
|
+
attr_accessor :data
|
81
|
+
attr_reader :ary
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# construct reactive value from normal value (or proc)
|
86
|
+
def Reac(obj=nil, &block)
|
87
|
+
if block
|
88
|
+
Reac::Proc.new(block)
|
89
|
+
else
|
90
|
+
if obj.is_a?(Array)
|
91
|
+
Reac::Array.new(obj)
|
92
|
+
else
|
93
|
+
Reac::Value.new(obj)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/sample/boxfont2.ttf
ADDED
Binary file
|
data/sample/clock.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'reac'
|
2
|
+
require 'reac/view/curses'
|
3
|
+
|
4
|
+
module Math
|
5
|
+
reactivize_singleton :sin, :cos
|
6
|
+
end
|
7
|
+
|
8
|
+
def rad(phase)
|
9
|
+
phase * (2 * Math::PI)
|
10
|
+
end
|
11
|
+
|
12
|
+
SIZE = 20
|
13
|
+
t = Reac{Time.now}
|
14
|
+
|
15
|
+
deg = (t.sec.to_f - 15) / 60
|
16
|
+
phase = rad(deg)
|
17
|
+
y = Math.sin(phase) * SIZE/2 + SIZE/2
|
18
|
+
x = Math.cos(phase) * SIZE + SIZE
|
19
|
+
|
20
|
+
Reac::CursesView.new{
|
21
|
+
put SIZE/2, SIZE-2, t.strftime("%H:%M")
|
22
|
+
put y, x, t.sec.to_s
|
23
|
+
}.start(30)
|
24
|
+
|
data/sample/icon.bmp
ADDED
Binary file
|
data/sample/reacirb.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'readline'
|
2
|
+
require 'reac'
|
3
|
+
require 'reac/view/sdl'
|
4
|
+
|
5
|
+
# ruby 1.8/1.9 compatible
|
6
|
+
# ord(?a) #=> returns 97
|
7
|
+
def ord(x)
|
8
|
+
if x.is_a? String
|
9
|
+
x.ord
|
10
|
+
else
|
11
|
+
x
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# call this before loading images, fonts, etc.
|
16
|
+
Reac::SDLView.init
|
17
|
+
view = Reac::SDLView.new
|
18
|
+
|
19
|
+
#font = SDL::BMFont.open("font.bmp", SDL::BMFont::TRANSPARENT)
|
20
|
+
font = SDL::TTF.open("ttf.ttf", 16)
|
21
|
+
#font = SDL::TTF.open("/Library/Fonts/Osaka.dfont", 16)
|
22
|
+
|
23
|
+
h = font.line_skip
|
24
|
+
n_lines = 0
|
25
|
+
|
26
|
+
#def method_missing(method, *args)
|
27
|
+
# eval("@#{method}")
|
28
|
+
#end
|
29
|
+
|
30
|
+
KEYS = {
|
31
|
+
SDL::Key::K1 => [ "1" , "!"] ,
|
32
|
+
SDL::Key::K2 => [ "2" , "\""] ,
|
33
|
+
SDL::Key::K3 => [ "3" , "#"] ,
|
34
|
+
SDL::Key::K4 => [ "4" , "$"] ,
|
35
|
+
SDL::Key::K5 => [ "5" , "%"] ,
|
36
|
+
SDL::Key::K6 => [ "6" , "&"] ,
|
37
|
+
SDL::Key::K7 => [ "7" , "'"] ,
|
38
|
+
SDL::Key::K8 => [ "8" , "("] ,
|
39
|
+
SDL::Key::K9 => [ "9" , ")"] ,
|
40
|
+
SDL::Key::K0 => [ "0" , "0"] ,
|
41
|
+
SDL::Key::MINUS => [ "-" , "="] ,
|
42
|
+
SDL::Key::CARET => [ "^" , "~"] ,
|
43
|
+
SDL::Key::BACKSLASH => [ "\\" , "_"] ,
|
44
|
+
|
45
|
+
SDL::Key::AT => [ "@" , "`"] ,
|
46
|
+
SDL::Key::LEFTBRACKET => [ "[" , "{"] ,
|
47
|
+
|
48
|
+
SDL::Key::SEMICOLON => [ ";" , "+"] ,
|
49
|
+
SDL::Key::COLON => [ ":" , "*"] ,
|
50
|
+
SDL::Key::RIGHTBRACKET => [ "]" , "}"] ,
|
51
|
+
|
52
|
+
SDL::Key::COMMA => [ "," , "<"] ,
|
53
|
+
SDL::Key::PERIOD => [ "." , ">"] ,
|
54
|
+
SDL::Key::SLASH => [ "/" , "?"] ,
|
55
|
+
}
|
56
|
+
str = "(press ESCAPE to exit)\n" +
|
57
|
+
"example: > @t = Reac{ Time.now }\n" +
|
58
|
+
"> "
|
59
|
+
header_size = str.split(/\n/).size
|
60
|
+
res = Reac{
|
61
|
+
while event = SDL::Event2.poll
|
62
|
+
if event.is_a?(SDL::Event2::KeyDown)
|
63
|
+
if KEYS.key?(event.sym)
|
64
|
+
if (event.mod & SDL::Key::MOD_SHIFT) == 0
|
65
|
+
str << KEYS[event.sym].first
|
66
|
+
else
|
67
|
+
str << KEYS[event.sym].last
|
68
|
+
end
|
69
|
+
else
|
70
|
+
case event.sym
|
71
|
+
when SDL::Key::BACKSPACE
|
72
|
+
str.chop!
|
73
|
+
when SDL::Key::A .. SDL::Key::Z
|
74
|
+
c = ord(?a) + (event.sym - SDL::Key::A)
|
75
|
+
if (event.mod & SDL::Key::MOD_SHIFT) != 0
|
76
|
+
c += (ord(?A) - ord(?a))
|
77
|
+
end
|
78
|
+
str << c.chr
|
79
|
+
when SDL::Key::COMMA .. SDL::Key::SEMICOLON
|
80
|
+
str << (ord(?,) + (event.sym - SDL::Key::COMMA)).chr
|
81
|
+
when SDL::Key::RETURN
|
82
|
+
code = str.split(/\n/).last[/^> (.*)/, 1]
|
83
|
+
y = h * (n_lines * 2 + header_size)
|
84
|
+
begin
|
85
|
+
view.put 0, y, self.instance_eval(code).inspect, font
|
86
|
+
rescue StandardError, ScriptError => e
|
87
|
+
view.put 0, y, "#{e.class}!"
|
88
|
+
end
|
89
|
+
n_lines += 1
|
90
|
+
str << "\n\n> "
|
91
|
+
when SDL::Key::SPACE
|
92
|
+
str << " "
|
93
|
+
when SDL::Key::ESCAPE
|
94
|
+
exit
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
str
|
100
|
+
}
|
101
|
+
|
102
|
+
view.put 0, 0, res, font
|
103
|
+
view.start
|
data/sample/sample.rb
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'reac'
|
2
|
+
require 'reac/view/curses.rb'
|
3
|
+
|
4
|
+
x = Reac{rand(100)}
|
5
|
+
y = x * 2
|
6
|
+
t = Reac{Time.now}
|
7
|
+
|
8
|
+
#### Make a reactive world (declarative style):
|
9
|
+
Reac::CursesView.new{
|
10
|
+
put 0, 0, "x : " + x.to_s
|
11
|
+
put 1, 0, "y : " + y.to_s
|
12
|
+
put 2, 0, "x and y : " + Reac([x, y]).inspect
|
13
|
+
|
14
|
+
put 4, 0, "now : " + t.strftime("%H:%M:%S")
|
15
|
+
put 5, 0, "second * 100 : " + (t.sec * 100).to_s
|
16
|
+
|
17
|
+
}.start(3)
|
18
|
+
|
19
|
+
#### Another way (imperative style):
|
20
|
+
#
|
21
|
+
#view = Reac::CursesView.new
|
22
|
+
#view.put(0, 0, "x : " + x.to_s)
|
23
|
+
#view.put(1, 0, "y : " + y.to_s)
|
24
|
+
#view.put(2, 0, "x and y : " + Reac([x, y]).inspect)
|
25
|
+
#
|
26
|
+
#view.put(4, 0, "now : " + t.strftime("%H:%M:%S"))
|
27
|
+
#view.put(5, 0, "second * 100 : " + (t.sec * 100).to_s)
|
28
|
+
#
|
29
|
+
#view.start(3)
|
data/sample/sdltest.rb
ADDED
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: reac
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- yhara (Yutaka HARA)
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2008-07-07 00:00:00 +09:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description:
|
17
|
+
email: yhara.at.kmc.gr.jp
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files: []
|
23
|
+
|
24
|
+
files:
|
25
|
+
- README.rdoc
|
26
|
+
- lib/reac/reactivize.rb
|
27
|
+
- lib/reac/view/curses.rb
|
28
|
+
- lib/reac/view/sdl.rb
|
29
|
+
- lib/reac/view.rb
|
30
|
+
- lib/reac.rb
|
31
|
+
- sample/boxfont2.ttf
|
32
|
+
- sample/clock.rb
|
33
|
+
- sample/icon.bmp
|
34
|
+
- sample/reacirb.rb
|
35
|
+
- sample/sample.rb
|
36
|
+
- sample/sdltest.rb
|
37
|
+
has_rdoc: false
|
38
|
+
homepage:
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options: []
|
41
|
+
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project: reac
|
59
|
+
rubygems_version: 1.2.0
|
60
|
+
signing_key:
|
61
|
+
specification_version: 2
|
62
|
+
summary: Reactive Programming in Ruby
|
63
|
+
test_files: []
|
64
|
+
|