iotas 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/iotas/room.rb ADDED
@@ -0,0 +1,192 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2012 Jérémy Zurcher <jeremy@asynk.ch>
5
+ #
6
+ # This file is part of iotas.
7
+ #
8
+ # iotas is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Affero General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # iotas is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Affero General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Affero General Public License
19
+ # along with iotas. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ #
22
+ module Iotas
23
+ #
24
+ ERROR_ROUTE_NS = 'routing error: no source'.freeze
25
+ ERROR_ROUTE_RRWD = 'routing error: right room, wrong door'.freeze
26
+ ERROR_ROUTE_DDWR = 'routing error: drill down, wrong room'.freeze
27
+ ERROR_ROUTE_TRWR = 'routing error: top room, wrong room'.freeze
28
+ ERROR_ROUTE_NDNL = 'routing error: no destination, no link'.freeze
29
+ ERROR_ROUTE_SND = 'routing error: system no destination'.freeze
30
+ #
31
+ class Room < Iota
32
+ #
33
+ def initialize n, p
34
+ super n, p
35
+ @iotas = {}
36
+ @links = {}
37
+ end
38
+ #
39
+ def to_json *a
40
+ {
41
+ 'kls' => self.class.name,
42
+ 'name' => @name,
43
+ 'iotas' => @iotas,
44
+ 'links' => @links
45
+ }.to_json *a
46
+ end
47
+ #
48
+ def self.json_create o
49
+ raise Iotas::Exception.new "JSON #{o['kls']} != #{self.name}" if o['kls'] != self.name
50
+ room = self.new o['name'], o['parent']
51
+ o['iotas'].each do |name,iota|
52
+ eval( iota['kls'] ).json_create(iota.merge!('parent'=>room))
53
+ end
54
+ o['links'].each do |src,links|
55
+ links.each do |link|
56
+ room.add_link Iotas::Link.json_create(link)
57
+ end
58
+ end
59
+ room
60
+ end
61
+ #
62
+ def add_iota s
63
+ raise Iotas::Exception.new "Iota #{s.name} already has #{s.parent.name} as parent" if not s.parent.nil? and s.parent!=self
64
+ raise Iotas::Exception.new "Iota #{s.name} already exists in #{path}" if @iotas.has_key? s.name
65
+ s.parent = self if s.parent.nil?
66
+ @iotas[s.name]=s
67
+ end
68
+ #
69
+ def add_link l
70
+ l.door = @iotas[l.src]
71
+ raise Iotas::Exception.new "Link source #{l.src} does not exist in #{path}" if l.door.nil?
72
+ (@links[l.src] ||= [])<< l
73
+ end
74
+ #
75
+ def start!
76
+ puts " * start #{path}" if @spin.debug_routing
77
+ @iotas.values.each do |iota| iota.start! end
78
+ end
79
+ #
80
+ def stop!
81
+ puts " * stop #{path}" if @spin.debug_routing
82
+ @iotas.values.each do |iota| iota.stop! end
83
+ end
84
+ #
85
+ def search_down spath
86
+ return self if spath==path
87
+ return nil if (spath=~/^#{path}\/(\w+)\/?/)!=0
88
+ if iota = @iotas[$1]
89
+ return iota if iota.path==spath # needed as Door doesn't implement #search_down
90
+ return iota.search_down spath
91
+ end
92
+ nil
93
+ end
94
+ #
95
+ def try_links p
96
+ puts " -> try_links ..." if @spin.debug_routing
97
+ links = @links[p.src.name]
98
+ return false if links.nil?
99
+ pending_link = nil
100
+ apply_link = false
101
+ links.each do |link|
102
+ apply_link = link.cond_fields.nil? # unconditional link
103
+ p.set_link_fields link.cond_fields if not apply_link
104
+ if apply_link or (p.link_value==link.cond_value)
105
+ # link matches !
106
+ if pending_link
107
+ p2 = @spin.require_p p.class
108
+ p2.clone_data p
109
+ p2.apply_link! link
110
+ send_p p2
111
+ end
112
+ pending_link = link
113
+ end
114
+ end
115
+ if pending_link
116
+ p.apply_link! pending_link
117
+ send_p p
118
+ end
119
+ (not pending_link.nil?)
120
+ end
121
+ #
122
+ def route_p p
123
+ if p.room.nil? or p.room==path
124
+ if door = @iotas[p.door]
125
+ p.dst_routed! door
126
+ else
127
+ p.error! Iotas::ERROR_ROUTE_RRWD
128
+ end
129
+ elsif (p.room=~/^#{path}\/(.*)/)==0
130
+ room, *more = $1.split Iotas::PATH_SEP
131
+ if child=@iotas[room]
132
+ child.route_p p
133
+ else
134
+ p.error! Iotas::ERROR_ROUTE_DDWR
135
+ end
136
+ elsif @parent
137
+ @parent.route_p p
138
+ else
139
+ p.error! Iotas::ERROR_ROUTE_TRWR
140
+ end
141
+ end
142
+ #
143
+ def send_p p
144
+ puts " * send_p #{(p.next_dst.nil? ? 'no dst' : p.next_dst)} ..." if @spin.debug_routing
145
+ if p.src.nil?
146
+ # do not route orphan particles !!
147
+ p.error! Iotas::ERROR_ROUTE_NS, @spin
148
+ elsif p.next_dst
149
+ p.split_dst!
150
+ if p.door
151
+ route_p p
152
+ else
153
+ # boomerang
154
+ p.dst_routed! p.src
155
+ end
156
+ elsif try_links p
157
+ return
158
+ else
159
+ p.error! Iotas::ERROR_ROUTE_NDNL
160
+ end
161
+ puts " -> #{p.dst.path}#{Iotas::ACT_SEP}#{p.action}" if @spin.debug_routing
162
+ @spin.post_p p
163
+ end
164
+ #
165
+ def send_sys_p p
166
+ puts " * send_sys_p #{(p.next_dst.nil? ? 'no dst' : p.next_dst)} ..." if @spin.debug_routing
167
+ if p.next_dst
168
+ p.split_dst!
169
+ if p.door
170
+ route_p p
171
+ elsif p.action
172
+ p.dst_routed! @spin
173
+ end
174
+ else
175
+ p.error! Iotas::ERROR_ROUTE_SND
176
+ end
177
+ puts " -> #{p.dst.path}#{Iotas::ACT_SEP}#{p.action}" if @spin.debug_routing
178
+ @spin.post_sys_p p
179
+ end
180
+ #
181
+ def process_sys_p p
182
+ if p.action==Iotas::SYS_ACT_ADD_LINK
183
+ add_link Iotas::Link.from_particle_data p
184
+ end
185
+ @spin.release_p p
186
+ end
187
+ #
188
+ end
189
+ #
190
+ end
191
+ #
192
+ # EOF
data/lib/iotas/spin.rb ADDED
@@ -0,0 +1,150 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2012 Jérémy Zurcher <jeremy@asynk.ch>
5
+ #
6
+ # This file is part of iotas.
7
+ #
8
+ # iotas is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Affero General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # iotas is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Affero General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Affero General Public License
19
+ # along with iotas. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ #
22
+ module Iotas
23
+ #
24
+ class Spin < Room
25
+ #
26
+ def initialize n, o={}
27
+ super n, nil
28
+ #
29
+ @pool = {} # per particle class free list
30
+ @sys_fifo = [] # system particles fifo list
31
+ @app_fifo = [] # application particles fifo list
32
+ #
33
+ @run = false
34
+ @hibernation = o['hibernation']||false
35
+ @hibernate_path = 'iotas-hibernate-'+n+'.json'
36
+ @debug_errors = o[:debug_errors]||o['debug_errors']||false
37
+ @debug_routing = o[:debug_routing]||o['debug_routing']||false
38
+ #
39
+ if not o.empty?
40
+ o['iotas'].each do |name,iota|
41
+ Iotas::Room.json_create(iota.merge!('parent'=>self))
42
+ end if o['iotas']
43
+ o['app_fifo'].each do |particle|
44
+ @app_fifo << Iotas::Particle.json_create(particle.merge!('spin'=>self))
45
+ end if o['app_fifo']
46
+ o['sys_fifo'].each do |particle|
47
+ @sys_fifo << Iotas::Particle.json_create(particle.merge!('spin'=>self))
48
+ end if o['sys_fifo']
49
+ end
50
+ end
51
+ #
52
+ attr_accessor :run, :hibernate_path, :debug_errors, :debug_routing
53
+ #
54
+ def to_json *a
55
+ {
56
+ 'kls' => self.class.name,
57
+ 'timestamp' => Time.now,
58
+ 'name' => @name,
59
+ 'hibernation' => @hibernation,
60
+ 'iotas' => @iotas,
61
+ 'sys_fifo' => @sys_fifo,
62
+ 'app_fifo' => @app_fifo,
63
+ 'debug_errors' => @debug_errors,
64
+ 'debug_routing' => @debug_routing
65
+ }.to_json(*a)
66
+ end
67
+ #
68
+ def self.json_create o
69
+ raise Iotas::Exception.new "JSON #{o['kls']} != #{self.name}" if o['kls'] != self.name
70
+ self.new o['name'], o
71
+ end
72
+ #
73
+ def clear!
74
+ @iotas.clear
75
+ @pool.clear
76
+ @sys_fifo.clear
77
+ @app_fifo.clear
78
+ end
79
+ #
80
+ #
81
+ def release_p p
82
+ # hope there is no circular loop
83
+ while p2=p.merged_shift
84
+ release_p p2
85
+ end
86
+ ( @pool[p.class] ||= [] ) << p
87
+ end
88
+ #
89
+ def require_p p_kls
90
+ l = @pool[p_kls]
91
+ return p_kls.new if l.nil?
92
+ p = l.pop
93
+ return p_kls.new if p.nil?
94
+ p.reset!
95
+ p
96
+ end
97
+ #
98
+ def post_p p
99
+ @app_fifo << p
100
+ end
101
+ #
102
+ def post_sys_p p
103
+ @sys_fifo << p
104
+ end
105
+ #
106
+ def process_sys_p p
107
+ if p.action==Iotas::SYS_ACT_HIBERNATE
108
+ stop!
109
+ hibernate! p[FIELD_HIBERNATE_PATH]
110
+ else
111
+ super p
112
+ end
113
+ end
114
+ #
115
+ def spin!
116
+ @iotas.values.each do |iota| iota.start! end unless @hibernation
117
+ @run = true
118
+ @hibernation = false
119
+ while @run and (@sys_fifo.length>0 or @app_fifo.length>0)
120
+ while @run and @sys_fifo.length>0
121
+ p = @sys_fifo.shift
122
+ p.dst.process_sys_p p
123
+ end
124
+ while @run and @app_fifo.length>0
125
+ p = @app_fifo.shift
126
+ p.dst.process_p p
127
+ break
128
+ end
129
+ end
130
+ @iotas.values.each do |iota| iota.stop! end unless @hibernation
131
+ end
132
+ #
133
+ def stop!
134
+ @run=false
135
+ end
136
+ #
137
+ def hibernate! path=nil
138
+ @hibernation = true
139
+ File.open(path||@hibernate_path,'w') do |f| f << JSON.pretty_generate(self) end
140
+ end
141
+ #
142
+ def self.resume! path
143
+ self.json_create JSON.load File.open(path,'r') { |f| f.read }
144
+ end
145
+ #
146
+ end
147
+ #
148
+ end
149
+ #
150
+ # EOF
data/lib/iotas.rb ADDED
@@ -0,0 +1,51 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2012 Jérémy Zurcher <jeremy@asynk.ch>
5
+ #
6
+ # This file is part of iotas.
7
+ #
8
+ # iotas is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Affero General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # iotas is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Affero General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Affero General Public License
19
+ # along with iotas. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ require 'version'
22
+ #
23
+ module Iotas
24
+ #
25
+ PATH_SEP = '/'.freeze
26
+ LINK_SEP = ','.freeze
27
+ ACT_SEP = '?'.freeze
28
+ #
29
+ ACT_GET = 'get'.freeze
30
+ ACT_ERROR = 'error'.freeze
31
+ #
32
+ SYS_ACT_HIBERNATE = 'hibernate'.freeze
33
+ SYS_ACT_ADD_LINK = 'sys_add_link'.freeze
34
+ #
35
+ FIELD_ERROR_MSG = 'edoors_error'.freeze
36
+ FIELD_HIBERNATE_PATH= 'hibernate_path'.freeze
37
+ #
38
+ class Exception < ::Exception; end
39
+ #
40
+ end
41
+ #
42
+ require 'json'
43
+ require 'iotas/particle'
44
+ require 'iotas/iota'
45
+ require 'iotas/room'
46
+ require 'iotas/spin'
47
+ require 'iotas/door'
48
+ require 'iotas/board'
49
+ require 'iotas/link'
50
+ #
51
+ # EOF
data/lib/version.rb ADDED
@@ -0,0 +1,28 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+ # Copyright 2012 Jérémy Zurcher <jeremy@asynk.ch>
5
+ #
6
+ # This file is part of iotas.
7
+ #
8
+ # iotas is free software: you can redistribute it and/or modify
9
+ # it under the terms of the GNU Affero General Public License as published by
10
+ # the Free Software Foundation, either version 3 of the License, or
11
+ # (at your option) any later version.
12
+ #
13
+ # iotas is distributed in the hope that it will be useful,
14
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
15
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
+ # GNU Affero General Public License for more details.
17
+ #
18
+ # You should have received a copy of the GNU Affero General Public License
19
+ # along with iotas. If not, see <http://www.gnu.org/licenses/>.
20
+
21
+ #
22
+ module Iotas
23
+ #
24
+ VERSION = "0.0.2"
25
+ #
26
+ end
27
+ #
28
+ # EOF
@@ -0,0 +1,95 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+
5
+ require 'spec_helper'
6
+ #
7
+ describe Iotas::Board do
8
+ #
9
+ before (:all) do
10
+ @spin = Iotas::Spin.new 'dom0'
11
+ end
12
+ #
13
+ before(:each) do
14
+ @spin.clear!
15
+ end
16
+ #
17
+ it "require_p release_p" do
18
+ board = Iotas::Board.new 'hell', @spin
19
+ p0 = board.require_p Iotas::Particle
20
+ p0.src.should be board
21
+ p1 = board.require_p Iotas::Particle
22
+ p1.src.should be board
23
+ (p0===p1).should be_false
24
+ board.release_p p0
25
+ p2 = board.require_p Iotas::Particle
26
+ p2.src.should be board
27
+ (p0===p2).should be_true
28
+ end
29
+ #
30
+ it "particle wait and merge" do
31
+ p0 = Iotas::Particle.new
32
+ p0['k0'] = 'v0'
33
+ p0['k1'] = 'neither'
34
+ p0['k2'] = 'v2'
35
+ p0.set_link_fields 'k0,k2'
36
+ p0.link_value.should eql 'v0v2'
37
+ p1 = Iotas::Particle.new
38
+ p1['k0'] = 'v0'
39
+ p1['k1'] = 'nore'
40
+ p1['k2'] = 'v2'
41
+ p1.set_link_fields 'k0,k2'
42
+ p1.link_value.should eql 'v0v2'
43
+ P0 = p0
44
+ P1 = p1
45
+ class Board0 < Iotas::Board
46
+ attr_reader :ok, :follow
47
+ def receive_p p
48
+ @ok = false
49
+ case p.action
50
+ when Iotas::ACT_FOLLOW
51
+ @follow = true
52
+ @ok = (p===P0 and p.merged(0)===P1)
53
+ else
54
+ @follow = false
55
+ @ok = (p===P1 and p.merged(0)===P0)
56
+ end
57
+ end
58
+ end
59
+ b0 = Board0.new 'door0', @spin
60
+ b0.process_p p0
61
+ p0.merged(0).should be_nil
62
+ b0.process_p p1
63
+ b0.ok.should be_true
64
+ b0.follow.should be_false
65
+ #
66
+ p1.merged_shift
67
+ #
68
+ b0.process_p p0
69
+ p0.merged(0).should be_nil
70
+ # need to set it to p0 too, so case in Board0 is ok
71
+ p0.set_dst! Iotas::ACT_FOLLOW
72
+ p0.split_dst!
73
+ p1.set_dst! Iotas::ACT_FOLLOW
74
+ p1.split_dst!
75
+ b0.process_p p1
76
+ b0.ok.should be_true
77
+ b0.follow.should be_true
78
+ end
79
+ #
80
+ it "board->json->board" do
81
+ board = Iotas::Board.new 'hell', @spin
82
+ p0 = Iotas::Particle.new
83
+ p1 = Iotas::Particle.new
84
+ p1['v0']=0
85
+ p1.set_link_fields 'v0'
86
+ board.process_p p0
87
+ board.process_p p1
88
+ hell = Iotas::Board.json_create( JSON.load( JSON.generate(board) ) )
89
+ board.name.should eql hell.name
90
+ JSON.generate(board).should eql JSON.generate(hell)
91
+ end
92
+ #
93
+ end
94
+ #
95
+ # EOF
data/spec/door_spec.rb ADDED
@@ -0,0 +1,94 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+
5
+ require 'spec_helper'
6
+ #
7
+ describe Iotas::Door do
8
+ #
9
+ before (:all) do
10
+ @spin = Iotas::Spin.new 'dom0'
11
+ end
12
+ #
13
+ before(:each) do
14
+ @spin.clear!
15
+ end
16
+ #
17
+ it "require_p release_p" do
18
+ door = Iotas::Door.new 'hell', @spin
19
+ p0 = door.require_p Iotas::Particle
20
+ p0.src.should be door
21
+ p1 = door.require_p Iotas::Particle
22
+ p1.src.should be door
23
+ (p0===p1).should be_false
24
+ door.release_p p0
25
+ p2 = door.require_p Iotas::Particle
26
+ p2.src.should be door
27
+ (p0===p2).should be_true
28
+ end
29
+ #
30
+ it "NoMethodError when receive_p not overridden" do
31
+ class Door0 < Iotas::Door
32
+ end
33
+ f = Fake.new 'fake', @spin
34
+ d0 = Door0.new 'door0', f
35
+ p0 = d0.require_p Iotas::Particle
36
+ lambda { d0.process_p p0 }.should raise_error(NoMethodError)
37
+ end
38
+ #
39
+ it "send_p, send_sys_p, release_p and release of lost particles" do
40
+ class Door0 < Iotas::Door
41
+ def receive_p p
42
+ case p.action
43
+ when 'RELEASE'
44
+ release_p p
45
+ when 'SEND'
46
+ send_p p
47
+ when 'SEND_SYS'
48
+ send_sys_p p
49
+ else
50
+ # lost!!
51
+ end
52
+ end
53
+ end
54
+ f = Fake.new 'fake', @spin
55
+ d0 = Door0.new 'door0', f
56
+ p0 = d0.require_p Iotas::Particle
57
+ #
58
+ p0.set_dst! 'SEND'
59
+ p0.split_dst!
60
+ d0.process_p p0
61
+ f.p.should eql p0
62
+ #
63
+ p0.set_dst! 'SEND_SYS'
64
+ p0.split_dst!
65
+ d0.process_p p0
66
+ f.sp.should eql p0
67
+ #
68
+ p0.set_dst! 'RELEASE'
69
+ p0.split_dst!
70
+ d0.process_p p0
71
+ p1 = d0.require_p Iotas::Particle
72
+ p1.should be p0
73
+ #
74
+ p0.set_dst! 'LOST'
75
+ p0.split_dst!
76
+ d0.process_p p0
77
+ p1 = d0.require_p Iotas::Particle
78
+ p1.should be p0
79
+ #
80
+ d0.process_sys_p p0
81
+ p1 = @spin.require_p Iotas::Particle
82
+ p1.should be p0
83
+ end
84
+ #
85
+ it "door->json->door" do
86
+ door = Iotas::Door.new 'hell', @spin
87
+ hell = Iotas::Door.json_create( JSON.load( JSON.generate(door) ) )
88
+ door.name.should eql hell.name
89
+ JSON.generate(door).should eql JSON.generate(hell)
90
+ end
91
+ #
92
+ end
93
+ #
94
+ # EOF
data/spec/link_spec.rb ADDED
@@ -0,0 +1,38 @@
1
+ #! /usr/bin/env ruby
2
+ # -*- coding: UTF-8 -*-
3
+ #
4
+
5
+ require 'spec_helper'
6
+ #
7
+ describe Iotas::Link do
8
+ #
9
+ it "from particle data" do
10
+ @spin = Iotas::Spin.new 'dom0'
11
+ p = @spin.require_p Iotas::Particle
12
+ p.set_data Iotas::LNK_SRC, 'input1'
13
+ p.set_data Iotas::LNK_DSTS, 'concat1?follow,output1'
14
+ p.set_data Iotas::LNK_FIELDS, 'f0,f2'
15
+ p.set_data Iotas::LNK_CONDF, 'f0,f1,f2'
16
+ p.set_data Iotas::LNK_CONDV, 'v0v1v2'
17
+ lnk = Iotas::Link.from_particle_data p
18
+ lnk.src.should eql 'input1'
19
+ lnk.dsts.should eql 'concat1?follow,output1'
20
+ lnk.fields.should eql 'f0,f2'
21
+ lnk.cond_fields.should eql 'f0,f1,f2'
22
+ lnk.cond_value.should eql 'v0v1v2'
23
+ end
24
+ #
25
+ it "link->json->link" do
26
+ link = Iotas::Link.new 'input1', 'concat1?follow,output1', 'f0,f2', 'f0,f1,f2', 'v0v1v2'
27
+ lnk = Iotas::Link.json_create( JSON.load( JSON.generate(link) ) )
28
+ link.src.should eql lnk.src
29
+ link.dsts.should eql lnk.dsts
30
+ link.fields.should eql lnk.fields
31
+ link.cond_fields.should eql lnk.cond_fields
32
+ link.cond_value.should eql lnk.cond_value
33
+ JSON.generate(link).should eql JSON.generate(lnk)
34
+ end
35
+ #
36
+ end
37
+ #
38
+ # EOF