reac 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|