andromeda 0.1 → 0.1.2
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/.gitignore +1 -0
- data/.rvmrc +1 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +17 -9
- data/Gemfile.lock +5 -0
- data/LICENSE.txt +1 -2
- data/README.md +150 -15
- data/ROADMAP.md +73 -0
- data/Rakefile +4 -6
- data/andromeda.gemspec +2 -2
- data/lib/andromeda.rb +52 -9
- data/lib/andromeda/atom.rb +105 -0
- data/lib/andromeda/cmd.rb +242 -0
- data/lib/andromeda/common.rb +0 -0
- data/lib/andromeda/copy_clone.rb +44 -0
- data/lib/andromeda/error.rb +42 -0
- data/lib/andromeda/guide.rb +50 -0
- data/lib/andromeda/guide_track.rb +98 -0
- data/lib/andromeda/id.rb +10 -83
- data/lib/andromeda/impl/atom.rb +47 -0
- data/lib/andromeda/impl/class_attr.rb +31 -0
- data/lib/andromeda/impl/proto_plan.rb +219 -0
- data/lib/andromeda/impl/to_s.rb +48 -0
- data/lib/andromeda/impl/xor_id.rb +89 -0
- data/lib/andromeda/kit.rb +172 -0
- data/lib/andromeda/map_reduce.rb +3 -0
- data/lib/andromeda/plan.rb +130 -0
- data/lib/andromeda/pool_guide.rb +70 -0
- data/lib/andromeda/spot.rb +132 -0
- data/lib/andromeda/sugar.rb +41 -0
- data/lib/andromeda/sync.rb +68 -0
- data/lib/andromeda/version.rb +1 -1
- data/yard_extensions/andromeda.rb +28 -0
- metadata +30 -13
- data/lib/andromeda/andromeda.rb +0 -225
- data/lib/andromeda/commando.rb +0 -106
- data/lib/andromeda/helpers.rb +0 -134
- data/lib/andromeda/join.rb +0 -48
- data/lib/andromeda/pools.rb +0 -69
- data/lib/andromeda/scope.rb +0 -38
@@ -0,0 +1,98 @@
|
|
1
|
+
module Andromeda
|
2
|
+
|
3
|
+
module Guides
|
4
|
+
|
5
|
+
class Guide
|
6
|
+
include Andromeda::Impl::To_S
|
7
|
+
|
8
|
+
def track(spot, label, suggested_track = nil)
|
9
|
+
raise NoMethodError
|
10
|
+
end
|
11
|
+
|
12
|
+
def provision(track, label, tags_in)
|
13
|
+
tags_out = Hash.new
|
14
|
+
tags_out[:scope] = ::Andromeda::Atom::Region.new unless tags_in[:scope]
|
15
|
+
tags_out[:label] = label
|
16
|
+
tags_out
|
17
|
+
end
|
18
|
+
|
19
|
+
def pack(plan, track, was_suggested = false)
|
20
|
+
if plan.frozen? then plan else plan.identical_copy end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
class Track
|
26
|
+
include Andromeda::Impl::To_S
|
27
|
+
|
28
|
+
def follow(scope, *args, &thunk)
|
29
|
+
scope.enter
|
30
|
+
begin
|
31
|
+
thunk.call *args
|
32
|
+
ensure
|
33
|
+
scope.leave
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class LocalGuide < Guide
|
39
|
+
include Singleton
|
40
|
+
|
41
|
+
def track(spot, key, suggested_track = nil)
|
42
|
+
return suggested_track if suggested_track
|
43
|
+
LocalTrack.instance
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class LocalTrack < Track
|
48
|
+
include Singleton
|
49
|
+
end
|
50
|
+
|
51
|
+
module DispatchingTrack
|
52
|
+
|
53
|
+
def follow(scope, *args, &thunk) ; dispatch(scope, *args, &thunk) end
|
54
|
+
|
55
|
+
def process(&thunk)
|
56
|
+
thunk.call
|
57
|
+
end
|
58
|
+
|
59
|
+
def dispatch(scope, *args, &thunk)
|
60
|
+
scope.enter
|
61
|
+
begin
|
62
|
+
process do
|
63
|
+
begin
|
64
|
+
thunk.call *args
|
65
|
+
ensure
|
66
|
+
scope.leave
|
67
|
+
end
|
68
|
+
end
|
69
|
+
rescue
|
70
|
+
# In case Thread.new fails
|
71
|
+
scope.leave
|
72
|
+
raise
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
protected :process
|
77
|
+
protected :dispatch
|
78
|
+
end
|
79
|
+
|
80
|
+
class SpawnGuide < Guide
|
81
|
+
include Singleton
|
82
|
+
|
83
|
+
def track(spot, label, suggested_track = nil)
|
84
|
+
SpawnTrack.instance
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class SpawnTrack < Track
|
89
|
+
include Singleton
|
90
|
+
include DispatchingTrack
|
91
|
+
|
92
|
+
protected
|
93
|
+
|
94
|
+
def process(&thunk) ; Thread.new { || Thread.current; thunk.call } end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
data/lib/andromeda/id.rb
CHANGED
@@ -1,92 +1,19 @@
|
|
1
1
|
module Andromeda
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
NUM_BYTES = 12
|
3
|
+
class Id < Impl::XorId
|
4
|
+
# Default length of generated ids
|
5
|
+
DEFAULT_NUM_BYTES = 8
|
7
6
|
|
8
|
-
|
7
|
+
# @param [Bool] random
|
8
|
+
def initialize(random = true)
|
9
|
+
super DEFAULT_NUM_BYTES, random
|
10
|
+
end
|
9
11
|
|
10
|
-
def initialize(len = NUM_BYTES, random = true, init_data = nil)
|
11
|
-
raise ArgumentError unless len.kind_of?(Fixnum)
|
12
|
-
raise ArgumentError unless len >= 0
|
13
|
-
|
14
|
-
@data = if init_data
|
15
|
-
init_data
|
16
|
-
else
|
17
|
-
if random
|
18
|
-
then len.times.map { Id.rnd_byte }
|
19
|
-
else len.times.map { 0 } end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
public
|
24
|
-
|
25
|
-
def length ; @data.length end
|
26
|
-
|
27
|
-
def zero?
|
28
|
-
each { |b| return false unless b == 0 }
|
29
|
-
true
|
30
|
-
end
|
31
|
-
|
32
|
-
def each ; this = self ; 0.upto(length-1).each { |i| yield this[i] } end
|
33
|
-
|
34
|
-
def each_with_index ; this = self ; 0.upto(length-1).each { |i| yield i, this[i] } end
|
35
|
-
|
36
|
-
def zip_bytes(b)
|
37
|
-
return ArgumentError unless same_id_kind?(b)
|
38
|
-
a = self
|
39
|
-
0.upto(length-1).each { |i| yield a[i], b[i] }
|
40
|
-
end
|
41
|
-
def [](key) ; @data[key] end
|
42
|
-
|
43
|
-
def same_id_kind?(obj) ; obj.kind_of?(Id) && obj.length == self.length end
|
44
|
-
|
45
|
-
# Compare self to b
|
46
|
-
# @param [Id] b
|
47
|
-
def eq?(b)
|
48
|
-
zip_bytes(b) { |i,j| return false if i != j }
|
49
|
-
true
|
50
|
-
end
|
51
|
-
|
52
|
-
alias_method :==, :eq?
|
53
|
-
|
54
|
-
# xor self and b's ids component-wise
|
55
|
-
# @param [Array<Fixnum>] b
|
56
|
-
# @return [Id]
|
57
|
-
def xor(b)
|
58
|
-
r = []
|
59
|
-
zip_bytes(b) { |i,j| r << (i ^ j) }
|
60
|
-
Id.new r.length, false, r
|
61
|
-
end
|
62
|
-
|
63
|
-
def to_s
|
64
|
-
r = "#<#{self.class}:"
|
65
|
-
each { |b| r << Id.twochars(b.to_s(16)) }
|
66
|
-
r << '>'
|
67
|
-
r
|
68
|
-
end
|
69
|
-
|
70
|
-
# @param [Fixnum] length
|
71
|
-
# @return [Id] random id
|
72
|
-
def self.gen(length = NUM_BYTES) ; Id.new length, true end
|
73
|
-
|
74
|
-
# @param [Fixnum] length
|
75
12
|
# @return [Id] empty (zero) id
|
76
|
-
def self.zero
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
def self.rnd_byte ; Random.rand(256) end
|
81
|
-
|
82
|
-
def self.twochars(s)
|
83
|
-
case s.length
|
84
|
-
when 0 then '00'
|
85
|
-
when 1 then "0#{s}"
|
86
|
-
else s
|
87
|
-
end
|
13
|
+
def self.zero
|
14
|
+
@id = self.new false unless defined? @id
|
15
|
+
@id
|
88
16
|
end
|
89
|
-
|
90
17
|
end
|
91
18
|
|
92
19
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Andromeda
|
2
|
+
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
class Atom < Atomic
|
6
|
+
include To_S
|
7
|
+
|
8
|
+
def initialize(init_val = nil)
|
9
|
+
super init_val
|
10
|
+
end
|
11
|
+
|
12
|
+
def empty? ; value.nil? end
|
13
|
+
def full? ; ! value.nil? end
|
14
|
+
|
15
|
+
def to_short_s ; "(#{To_S.short_s(value)})" end
|
16
|
+
alias_method :inspect, :to_s
|
17
|
+
|
18
|
+
def wait_while(&test)
|
19
|
+
while test.call(value) ; Thread::pass end
|
20
|
+
end
|
21
|
+
|
22
|
+
def wait_until_eq(val = nil)
|
23
|
+
raise ArgumentError unless val.kind_of?(Fixnum)
|
24
|
+
wait_while { |v| v != val }
|
25
|
+
end
|
26
|
+
|
27
|
+
def wait_until_ne(val = nil)
|
28
|
+
raise ArgumentError unless val.kind_of?(Fixnum)
|
29
|
+
wait_while { |v| v == val }
|
30
|
+
end
|
31
|
+
|
32
|
+
def wait_until_empty?
|
33
|
+
wait_until_eq nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def wait_until_full?
|
37
|
+
wait_until_ne nil
|
38
|
+
end
|
39
|
+
|
40
|
+
def with_value
|
41
|
+
update { |v| yield v ; v }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Andromeda
|
2
|
+
|
3
|
+
module Impl
|
4
|
+
|
5
|
+
module ClassAttr
|
6
|
+
|
7
|
+
protected
|
8
|
+
|
9
|
+
def get_attr_set(var_name, inherit = true)
|
10
|
+
s = if instance_variable_defined?(var_name)
|
11
|
+
then instance_variable_get(var_name)
|
12
|
+
else Set.new end
|
13
|
+
if inherit
|
14
|
+
c = self
|
15
|
+
while (c = c.superclass)
|
16
|
+
s = s.union c.get_attr_set(var_name, false) rescue s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
return s
|
20
|
+
end
|
21
|
+
|
22
|
+
def name_attr_set(var_name, *names)
|
23
|
+
name_set = names.to_set
|
24
|
+
dest_set = get_attr_set var_name, false
|
25
|
+
instance_variable_set var_name, dest_set.union(name_set)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,219 @@
|
|
1
|
+
|
2
|
+
module Andromeda
|
3
|
+
|
4
|
+
module Impl
|
5
|
+
|
6
|
+
class ProtoPlan < Impl::ConnectorBase
|
7
|
+
include Impl::To_S
|
8
|
+
extend Impl::ClassAttr
|
9
|
+
|
10
|
+
def self.spot_names(inherit = true) ; get_attr_set '@spot_names', inherit end
|
11
|
+
def self.attr_spot_names(inherit = true) ; get_attr_set '@attr_spot_names', inherit end
|
12
|
+
def self.meth_spot_names(inherit = true) ; get_attr_set '@meth_spot_names', inherit end
|
13
|
+
def self.name_spot(*names) ; name_attr_set '@spot_names', *names end
|
14
|
+
|
15
|
+
def self.meth_spot(name, opts = {})
|
16
|
+
name_spot name
|
17
|
+
name_attr_set '@meth_spot_names', name
|
18
|
+
define_method :"#{name}" do ||
|
19
|
+
mk_spot name, opts = {}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.attr_spot(*names)
|
24
|
+
name_spot *names
|
25
|
+
name_attr_set '@attr_spot_names', *names
|
26
|
+
attr_writer *names
|
27
|
+
names.each do |name|
|
28
|
+
define_method :"#{name}" do ||
|
29
|
+
intern (instance_variable_get("@#{name}"))
|
30
|
+
end
|
31
|
+
define_method :"#{name}=" do |val|
|
32
|
+
if val
|
33
|
+
then instance_variable_set "@#{name}", intern(val.entry)
|
34
|
+
else instance_variable_set "@#{name}", nil end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.signal_names(inherit = true) ; get_attr_set '@signal_names', inherit end
|
40
|
+
def self.signal_spot(*names) ; name_attr_set '@signal_names', *names end
|
41
|
+
|
42
|
+
attr_reader :id
|
43
|
+
attr_reader :guide
|
44
|
+
|
45
|
+
def initialize(config = {})
|
46
|
+
@id = Id.new
|
47
|
+
set_from_config init_from_config, config
|
48
|
+
@tags ||= {}
|
49
|
+
@guide ||= init_guide
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize_copy(other)
|
53
|
+
@tags = @tags.identical_copy
|
54
|
+
@tags ||= {}
|
55
|
+
end
|
56
|
+
|
57
|
+
def init_guide ; Guides::DefaultGuide.instance end
|
58
|
+
|
59
|
+
def tags ; @tags end
|
60
|
+
def to_short_s ; " id=#{id.to_short_s}t" end
|
61
|
+
alias_method :inspect, :to_s
|
62
|
+
|
63
|
+
def guide=(new_guide)
|
64
|
+
new_guide = new_guide.instance if new_guide.is_a?(Class) && new_guide.include?(Singleton)
|
65
|
+
@guide = new_guide
|
66
|
+
end
|
67
|
+
|
68
|
+
# Overload to map all incoming data, default to data
|
69
|
+
def data_map(name, data) ; data end
|
70
|
+
|
71
|
+
# Overload to extract the data key from mapped, incoming data, defaults to name
|
72
|
+
def data_key(name, data) ; name end
|
73
|
+
|
74
|
+
# Overload to determine the target spot name from the key, defaults to name
|
75
|
+
def key_spot(name, key) ; name end
|
76
|
+
|
77
|
+
# Overload to determine the target track label from the key, defaults to ket
|
78
|
+
def key_label(name, key) ; key end
|
79
|
+
|
80
|
+
# Overload to extract the data value from mapped, incoming data, defaults to data
|
81
|
+
def data_val(name, data) ; data end
|
82
|
+
|
83
|
+
# Overload to compute additional tags
|
84
|
+
def data_tag(name, key, val, tags_in) ; { name: name } end
|
85
|
+
|
86
|
+
# Overload to filter the data events that should be processed, defaults to true
|
87
|
+
def selects?(name, key, val, tags_in) ; true end
|
88
|
+
|
89
|
+
def post_data(spot_, track_in, data, tags_in = {})
|
90
|
+
raise ArgumentError, "#{spot_} is not a Spot" unless spot_.is_a?(Spot)
|
91
|
+
raise ArgumentError, "#{spot_} is not a Spot of this Plan" unless spot_.plan == self
|
92
|
+
|
93
|
+
name = spot_.name
|
94
|
+
details = { name: name, data: data, tags_in: tags_in, spot: spot_ }
|
95
|
+
begin
|
96
|
+
data = data_map name, data
|
97
|
+
key = data_key name, data
|
98
|
+
name = key_spot name, key
|
99
|
+
|
100
|
+
guide_ = guide
|
101
|
+
label = key_label name, key
|
102
|
+
details[:label] = label
|
103
|
+
track_ = guide_.track spot_, label, track_in
|
104
|
+
details[:track] = track_
|
105
|
+
|
106
|
+
value = data_val name, data
|
107
|
+
details[:val] = value
|
108
|
+
tags_in.update data_tag name, key, value, tags_in
|
109
|
+
tags_in.update guide_.provision track_, label, tags_in
|
110
|
+
|
111
|
+
pack_ = guide_.pack self, track_, track_.equal?(track_in)
|
112
|
+
details[:pack] = pack_
|
113
|
+
meth = pack_.method :"on_#{name}"
|
114
|
+
|
115
|
+
if selects? name, key, value, tags_in
|
116
|
+
pack_.transport_data name, track_, meth, key, value, tags_in
|
117
|
+
end
|
118
|
+
rescue Exception => e
|
119
|
+
raise SendError, InfoMsg.str('send_data failed', details, e), e.backtrace
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# @return [Spot, nil] *public* spot with name name if any, nil otherwise
|
124
|
+
#
|
125
|
+
def public_spot(name)
|
126
|
+
raise ArgumentError, "#{name} is not a Symbol" unless name.is_a? Symbol
|
127
|
+
raise ArgumentError, "#{name} is not a known spot name" unless spot_name? name
|
128
|
+
|
129
|
+
if respond_to?(name)
|
130
|
+
then intern public_send(name)
|
131
|
+
else nil
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def spot_name?(name) ; spot_names.include? name end
|
136
|
+
def attr_spot_name?(name) ; attr_spot_names.include? name end
|
137
|
+
def meth_spot_name?(name) ; meth_spot_names.include? name end
|
138
|
+
def signal_name?(name) ; signal_names.include? name end
|
139
|
+
|
140
|
+
def spot_names ; self.class.spot_names end
|
141
|
+
def attr_spot_names ; self.class.attr_spot_names end
|
142
|
+
def meth_spot_names ; self.class.meth_spot_names end
|
143
|
+
def signal_names ; self.class.signal_names end
|
144
|
+
|
145
|
+
def current_scope ; tags[:scope] end
|
146
|
+
def current_name ; tags[:name] end
|
147
|
+
|
148
|
+
def >>(spot) ; @emit = spot.entry ; spot.dest end
|
149
|
+
|
150
|
+
def entry ; enter end
|
151
|
+
def dest ; emit end
|
152
|
+
def mute ; @emit = nil end
|
153
|
+
def via(spot_name) ; entry.via(spot_name) end
|
154
|
+
|
155
|
+
def post_to(track, data, tags_in = {}) ; start.post_to track, data, tags_in end
|
156
|
+
|
157
|
+
protected
|
158
|
+
|
159
|
+
attr_spot :emit
|
160
|
+
|
161
|
+
def exit ; emit end
|
162
|
+
|
163
|
+
def init_from_config ; [:readers, :writers] end
|
164
|
+
|
165
|
+
def set_from_config(what, config = {})
|
166
|
+
init_readers = what.include? :readers
|
167
|
+
init_writers = what.include? :writers
|
168
|
+
config.each_pair do |k, v|
|
169
|
+
k = k.to_sym rescue nil
|
170
|
+
if init_writers
|
171
|
+
writer = :"#{k}=" rescue nil
|
172
|
+
if writer && self.respond_to?(writer)
|
173
|
+
then self.send writer, v
|
174
|
+
else instance_variable_set "@#{k}", v if init_readers && self.respond_to?(k) end
|
175
|
+
else
|
176
|
+
instance_variable_set "@#{k}", v if init_readers && self.respond_to?(k)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Call local method spot with name spot_name with key and val without any preprocessing
|
182
|
+
#
|
183
|
+
# Requires that spot_name resolves to a spot of this plan
|
184
|
+
#
|
185
|
+
def call_local(spot_name, key, val)
|
186
|
+
spot_ = spot spot_name
|
187
|
+
raise ArgumentError, "#{name} could not be resolved to a Spot" unless spot_
|
188
|
+
raise ArgumenError, "Cannot call_local for other Plans" unless spot_.plan == self
|
189
|
+
send :"on_#{spot_name}", key, val
|
190
|
+
end
|
191
|
+
|
192
|
+
# @return [Spot, nil] *public* spot with name name if any, nil otherwise
|
193
|
+
#
|
194
|
+
def spot(name)
|
195
|
+
raise ArgumentError, "#{name} is not a Symbol" unless name.is_a? Symbol
|
196
|
+
raise ArgumentError, "#{name} is not a known spot name" unless spot_name? name
|
197
|
+
|
198
|
+
if respond_to?(name)
|
199
|
+
then intern send(name)
|
200
|
+
else nil
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def mk_spot(name, opts = {})
|
205
|
+
Spot.new self, name, self, opts[:dest]
|
206
|
+
end
|
207
|
+
|
208
|
+
def intern(spot)
|
209
|
+
return nil unless spot
|
210
|
+
raise ArgumentError unless spot.is_a? Spot
|
211
|
+
spot.intern(self)
|
212
|
+
end
|
213
|
+
|
214
|
+
def signal_error(e) ; raise e end
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|