rx_ruby 0.0.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.
- checksums.yaml +7 -0
- data/.gitattributes +22 -0
- data/.gitignore +173 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/Rakefile +11 -0
- data/examples/aggregate.rb +39 -0
- data/examples/amb.rb +25 -0
- data/examples/ambproto.rb +24 -0
- data/examples/and.rb +26 -0
- data/examples/as_observable.rb +25 -0
- data/examples/average.rb +43 -0
- data/examples/buffer_with_count.rb +44 -0
- data/examples/buffer_with_time.rb +51 -0
- data/examples/case.rb +29 -0
- data/examples/catch.rb +20 -0
- data/examples/catchproto.rb +39 -0
- data/examples/combine_latest.rb +35 -0
- data/examples/combine_latestproto.rb +33 -0
- data/examples/concat.rb +22 -0
- data/examples/concat_all.rb +27 -0
- data/examples/concat_map.rb +61 -0
- data/examples/concat_map_observer.rb +29 -0
- data/examples/concatproto.rb +25 -0
- data/examples/connect.rb +41 -0
- data/examples/contains.rb +37 -0
- data/examples/count.rb +36 -0
- data/examples/create.rb +55 -0
- data/examples/debounce.rb +35 -0
- data/examples/default_if_empty.rb +35 -0
- data/examples/defer.rb +20 -0
- data/examples/delay.rb +49 -0
- data/examples/delay_with_selector.rb +63 -0
- data/examples/dematerialize.rb +22 -0
- data/examples/disposable.rb +12 -0
- data/examples/distinct.rb +43 -0
- data/examples/distinct_until_changed.rb +43 -0
- data/examples/do.rb +59 -0
- data/examples/empty.rb +16 -0
- data/examples/for.rb +26 -0
- data/examples/fork_join.rb +23 -0
- data/examples/from.rb +106 -0
- data/examples/from_array.rb +21 -0
- data/examples/from_callback.rb +21 -0
- data/examples/generate.rb +24 -0
- data/examples/group_join.rb +39 -0
- data/examples/if.rb +46 -0
- data/examples/intervals.rb +26 -0
- data/examples/merge.rb +36 -0
- data/examples/merge_all.rb +27 -0
- data/examples/multicast.rb +32 -0
- data/examples/never.rb +15 -0
- data/examples/of.rb +19 -0
- data/examples/on_error_resume_next.rb +21 -0
- data/examples/pairs.rb +26 -0
- data/examples/publish.rb +79 -0
- data/examples/range.rb +19 -0
- data/examples/reduce.rb +18 -0
- data/examples/repeat.rb +19 -0
- data/examples/return.rb +17 -0
- data/examples/scan.rb +41 -0
- data/examples/start.rb +29 -0
- data/examples/throw.rb +17 -0
- data/examples/time_intervals.rb +28 -0
- data/examples/timer.rb +26 -0
- data/examples/timestamp.rb +28 -0
- data/examples/to_a.rb +23 -0
- data/examples/to_async.rb +26 -0
- data/examples/using.rb +52 -0
- data/examples/when.rb +26 -0
- data/examples/while.rb +25 -0
- data/examples/window_with_time.rb +78 -0
- data/examples/zip.rb +27 -0
- data/examples/zip_array.rb +25 -0
- data/lib/core_ext/enumerable.rb +22 -0
- data/lib/rx_ruby.rb +27 -0
- data/lib/rx_ruby/concurrency/async_lock.rb +57 -0
- data/lib/rx_ruby/concurrency/current_thread_scheduler.rb +75 -0
- data/lib/rx_ruby/concurrency/default_scheduler.rb +51 -0
- data/lib/rx_ruby/concurrency/historical_scheduler.rb +16 -0
- data/lib/rx_ruby/concurrency/immediate_scheduler.rb +68 -0
- data/lib/rx_ruby/concurrency/local_scheduler.rb +39 -0
- data/lib/rx_ruby/concurrency/periodic_scheduler.rb +74 -0
- data/lib/rx_ruby/concurrency/scheduled_item.rb +42 -0
- data/lib/rx_ruby/concurrency/scheduler.rb +150 -0
- data/lib/rx_ruby/concurrency/virtual_time_scheduler.rb +170 -0
- data/lib/rx_ruby/core/async_lock_observer.rb +46 -0
- data/lib/rx_ruby/core/auto_detach_observer.rb +59 -0
- data/lib/rx_ruby/core/checked_observer.rb +66 -0
- data/lib/rx_ruby/core/notification.rb +161 -0
- data/lib/rx_ruby/core/observable.rb +104 -0
- data/lib/rx_ruby/core/observe_on_observer.rb +50 -0
- data/lib/rx_ruby/core/observer.rb +119 -0
- data/lib/rx_ruby/core/scheduled_observer.rb +83 -0
- data/lib/rx_ruby/core/synchronized_observer.rb +47 -0
- data/lib/rx_ruby/core/time_interval.rb +17 -0
- data/lib/rx_ruby/internal/priority_queue.rb +122 -0
- data/lib/rx_ruby/internal/util.rb +9 -0
- data/lib/rx_ruby/joins/active_plan.rb +45 -0
- data/lib/rx_ruby/joins/join_observer.rb +51 -0
- data/lib/rx_ruby/joins/pattern.rb +14 -0
- data/lib/rx_ruby/joins/plan.rb +44 -0
- data/lib/rx_ruby/linq/connectable_observable.rb +34 -0
- data/lib/rx_ruby/linq/observable/_observable_timer_date_and_period.rb +22 -0
- data/lib/rx_ruby/linq/observable/_observable_timer_time_span.rb +14 -0
- data/lib/rx_ruby/linq/observable/_observable_timer_time_span_and_period.rb +20 -0
- data/lib/rx_ruby/linq/observable/aggregate.rb +7 -0
- data/lib/rx_ruby/linq/observable/and.rb +7 -0
- data/lib/rx_ruby/linq/observable/case.rb +15 -0
- data/lib/rx_ruby/linq/observable/concat_all.rb +7 -0
- data/lib/rx_ruby/linq/observable/concat_map.rb +35 -0
- data/lib/rx_ruby/linq/observable/concat_map_observer.rb +43 -0
- data/lib/rx_ruby/linq/observable/contains.rb +28 -0
- data/lib/rx_ruby/linq/observable/debounce.rb +41 -0
- data/lib/rx_ruby/linq/observable/delay.rb +81 -0
- data/lib/rx_ruby/linq/observable/delay_with_selector.rb +64 -0
- data/lib/rx_ruby/linq/observable/do.rb +42 -0
- data/lib/rx_ruby/linq/observable/for.rb +13 -0
- data/lib/rx_ruby/linq/observable/fork_join.rb +55 -0
- data/lib/rx_ruby/linq/observable/from.rb +34 -0
- data/lib/rx_ruby/linq/observable/group_join.rb +108 -0
- data/lib/rx_ruby/linq/observable/if.rb +17 -0
- data/lib/rx_ruby/linq/observable/interval.rb +5 -0
- data/lib/rx_ruby/linq/observable/multicast.rb +14 -0
- data/lib/rx_ruby/linq/observable/of.rb +11 -0
- data/lib/rx_ruby/linq/observable/pairs.rb +7 -0
- data/lib/rx_ruby/linq/observable/pluck.rb +7 -0
- data/lib/rx_ruby/linq/observable/publish.rb +11 -0
- data/lib/rx_ruby/linq/observable/start.rb +7 -0
- data/lib/rx_ruby/linq/observable/time_interval.rb +15 -0
- data/lib/rx_ruby/linq/observable/timer.rb +26 -0
- data/lib/rx_ruby/linq/observable/timestamp.rb +9 -0
- data/lib/rx_ruby/linq/observable/to_async.rb +40 -0
- data/lib/rx_ruby/linq/observable/when.rb +36 -0
- data/lib/rx_ruby/linq/observable/while.rb +41 -0
- data/lib/rx_ruby/operators/aggregates.rb +611 -0
- data/lib/rx_ruby/operators/creation.rb +220 -0
- data/lib/rx_ruby/operators/multiple.rb +735 -0
- data/lib/rx_ruby/operators/single.rb +399 -0
- data/lib/rx_ruby/operators/standard_query_operators.rb +279 -0
- data/lib/rx_ruby/operators/synchronization.rb +47 -0
- data/lib/rx_ruby/operators/time.rb +120 -0
- data/lib/rx_ruby/subjects/async_subject.rb +161 -0
- data/lib/rx_ruby/subjects/behavior_subject.rb +149 -0
- data/lib/rx_ruby/subjects/replay_subject.rb +39 -0
- data/lib/rx_ruby/subjects/subject.rb +131 -0
- data/lib/rx_ruby/subjects/subject_extensions.rb +45 -0
- data/lib/rx_ruby/subscriptions/composite_subscription.rb +91 -0
- data/lib/rx_ruby/subscriptions/ref_count_subscription.rb +88 -0
- data/lib/rx_ruby/subscriptions/scheduled_subscription.rb +32 -0
- data/lib/rx_ruby/subscriptions/serial_subscription.rb +60 -0
- data/lib/rx_ruby/subscriptions/single_assignment_subscription.rb +64 -0
- data/lib/rx_ruby/subscriptions/subscription.rb +56 -0
- data/lib/rx_ruby/testing/cold_observable.rb +45 -0
- data/lib/rx_ruby/testing/hot_observable.rb +47 -0
- data/lib/rx_ruby/testing/mock_observer.rb +33 -0
- data/lib/rx_ruby/testing/reactive_test.rb +94 -0
- data/lib/rx_ruby/testing/recorded.rb +17 -0
- data/lib/rx_ruby/testing/test_scheduler.rb +96 -0
- data/lib/rx_ruby/testing/test_subscription.rb +22 -0
- data/lib/rx_ruby/version.rb +3 -0
- data/license.txt +13 -0
- data/readme.md +152 -0
- data/rx_ruby.gemspec +22 -0
- data/test/rx_ruby/concurrency/helpers/historical_virtual_scheduler_helper.rb +135 -0
- data/test/rx_ruby/concurrency/helpers/immediate_local_scheduler_helper.rb +51 -0
- data/test/rx_ruby/concurrency/test_async_lock.rb +56 -0
- data/test/rx_ruby/concurrency/test_current_thread_scheduler.rb +44 -0
- data/test/rx_ruby/concurrency/test_default_scheduler.rb +44 -0
- data/test/rx_ruby/concurrency/test_historical_scheduler.rb +18 -0
- data/test/rx_ruby/concurrency/test_immediate_scheduler.rb +53 -0
- data/test/rx_ruby/concurrency/test_local_scheduler.rb +12 -0
- data/test/rx_ruby/concurrency/test_periodic_scheduler.rb +53 -0
- data/test/rx_ruby/concurrency/test_scheduled_item.rb +50 -0
- data/test/rx_ruby/concurrency/test_scheduler.rb +128 -0
- data/test/rx_ruby/concurrency/test_virtual_time_scheduler.rb +14 -0
- data/test/rx_ruby/core/test_notification.rb +129 -0
- data/test/rx_ruby/core/test_observable_creation.rb +483 -0
- data/test/rx_ruby/core/test_observer.rb +634 -0
- data/test/rx_ruby/internal/test_priority_queue.rb +71 -0
- data/test/rx_ruby/subscriptions/test_composite_subscription.rb +116 -0
- data/test/rx_ruby/subscriptions/test_serial_subscription.rb +62 -0
- data/test/rx_ruby/subscriptions/test_singleassignment_subscription.rb +61 -0
- data/test/rx_ruby/subscriptions/test_subscription.rb +27 -0
- data/test/test_helper.rb +11 -0
- metadata +291 -0
@@ -0,0 +1,220 @@
|
|
1
|
+
# Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
2
|
+
|
3
|
+
require 'rx_ruby/concurrency/current_thread_scheduler'
|
4
|
+
require 'rx_ruby/concurrency/immediate_scheduler'
|
5
|
+
require 'rx_ruby/subscriptions/composite_subscription'
|
6
|
+
require 'rx_ruby/subscriptions/subscription'
|
7
|
+
|
8
|
+
module RxRuby
|
9
|
+
|
10
|
+
module Observable
|
11
|
+
|
12
|
+
# Creation Operators
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
# Creates an observable sequence from a specified subscribe method implementation.
|
17
|
+
def create(&subscribe)
|
18
|
+
AnonymousObservable.new do |observer|
|
19
|
+
subscription = subscribe.call(observer)
|
20
|
+
case subscription
|
21
|
+
when Subscription
|
22
|
+
subscription
|
23
|
+
when Proc
|
24
|
+
Subscription.create(&subscription)
|
25
|
+
else
|
26
|
+
Subscription.empty
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns an observable sequence that invokes the specified factory function whenever a new observer subscribes.
|
32
|
+
def defer
|
33
|
+
AnonymousObservable.new do |observer|
|
34
|
+
result = nil
|
35
|
+
e = nil
|
36
|
+
begin
|
37
|
+
result = yield
|
38
|
+
rescue => err
|
39
|
+
e = Observable.raise_error(err).subscribe(observer)
|
40
|
+
end
|
41
|
+
|
42
|
+
e || result.subscribe(observer)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns an empty observable sequence, using the specified scheduler to send out the single OnCompleted message.
|
47
|
+
def empty(scheduler = ImmediateScheduler.instance)
|
48
|
+
AnonymousObservable.new do |observer|
|
49
|
+
scheduler.schedule lambda {
|
50
|
+
observer.on_completed
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Generates an observable sequence by running a state-driven loop producing the sequence's elements.
|
56
|
+
def generate(initial_state, condition, iterate, result_selector, scheduler = CurrentThreadScheduler.instance)
|
57
|
+
AnonymousObservable.new do |observer|
|
58
|
+
state = initial_state
|
59
|
+
first = true
|
60
|
+
|
61
|
+
scheduler.schedule_recursive lambda{|this|
|
62
|
+
has_result = false
|
63
|
+
result = nil
|
64
|
+
begin
|
65
|
+
|
66
|
+
if first
|
67
|
+
first = false
|
68
|
+
else
|
69
|
+
state = iterate.call(state)
|
70
|
+
end
|
71
|
+
|
72
|
+
has_result = condition.call(state)
|
73
|
+
|
74
|
+
if has_result
|
75
|
+
result = result_selector.call state
|
76
|
+
end
|
77
|
+
rescue => err
|
78
|
+
observer.on_error err
|
79
|
+
return
|
80
|
+
end
|
81
|
+
if has_result
|
82
|
+
observer.on_next result
|
83
|
+
this.call
|
84
|
+
else
|
85
|
+
observer.on_completed
|
86
|
+
end
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# Returns a non-terminating observable sequence, which can be used to denote an infinite duration (e.g. when using reactive joins).
|
92
|
+
def never
|
93
|
+
AnonymousObservable.new do |_|
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns an observable sequence that contains a single element.
|
99
|
+
def just(value, scheduler = ImmediateScheduler.instance)
|
100
|
+
AnonymousObservable.new do |observer|
|
101
|
+
scheduler.schedule lambda {
|
102
|
+
observer.on_next value
|
103
|
+
observer.on_completed
|
104
|
+
}
|
105
|
+
end
|
106
|
+
end
|
107
|
+
alias :return :just
|
108
|
+
|
109
|
+
# Converts an array to an observable sequence, using an optional scheduler to enumerate the array.
|
110
|
+
def of_array(array, scheduler = CurrentThreadScheduler.instance)
|
111
|
+
AnonymousObservable.new do |observer|
|
112
|
+
count = 0
|
113
|
+
scheduler.schedule_recursive lambda {|this|
|
114
|
+
if count < array.length
|
115
|
+
observer.on_next array[count]
|
116
|
+
count += 1
|
117
|
+
this.call
|
118
|
+
else
|
119
|
+
observer.on_completed
|
120
|
+
end
|
121
|
+
}
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Converts an Enumerable to an observable sequence, using an optional scheduler to enumerate the array.
|
126
|
+
def of_enumerable(enumerable, scheduler = CurrentThreadScheduler.instance)
|
127
|
+
Observable.of_enumerator(enumerable.to_enum, scheduler)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Converts an Enumerator to an observable sequence, using an optional scheduler to enumerate the array.
|
131
|
+
def of_enumerator(enum, scheduler = CurrentThreadScheduler.instance)
|
132
|
+
AnonymousObservable.new do |observer|
|
133
|
+
scheduler.schedule_recursive lambda {|this|
|
134
|
+
has_value = false
|
135
|
+
value = nil
|
136
|
+
|
137
|
+
begin
|
138
|
+
value = enum.next
|
139
|
+
has_value = true
|
140
|
+
rescue StopIteration => _
|
141
|
+
observer.on_completed
|
142
|
+
rescue => e
|
143
|
+
observer.on_error e
|
144
|
+
end
|
145
|
+
|
146
|
+
if has_value
|
147
|
+
observer.on_next value
|
148
|
+
this.call
|
149
|
+
end
|
150
|
+
}
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Returns an observable sequence that terminates with an exception.
|
155
|
+
def raise_error(error, scheduler = ImmediateScheduler.instance)
|
156
|
+
AnonymousObservable.new do |observer|
|
157
|
+
scheduler.schedule lambda {
|
158
|
+
observer.on_error error
|
159
|
+
}
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Generates an observable sequence of integral numbers within a specified range.
|
164
|
+
def range(start, count, scheduler = CurrentThreadScheduler.instance)
|
165
|
+
AnonymousObservable.new do |observer|
|
166
|
+
scheduler.schedule_recursive_with_state 0, lambda {|i, this|
|
167
|
+
if i < count
|
168
|
+
observer.on_next (start + i)
|
169
|
+
this.call(i + 1)
|
170
|
+
else
|
171
|
+
observer.on_completed
|
172
|
+
end
|
173
|
+
}
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
# Generates an observable sequence that repeats the given element infinitely.
|
178
|
+
def repeat_infinitely(value, scheduler = CurrentThreadScheduler.instance)
|
179
|
+
Observable.just(value, scheduler).repeat_infinitely
|
180
|
+
end
|
181
|
+
|
182
|
+
# Generates an observable sequence that repeats the given element the specified number of times.
|
183
|
+
def repeat(value, count, scheduler = CurrentThreadScheduler.instance)
|
184
|
+
Observable.just(value, scheduler).repeat(count)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Constructs an observable sequence that depends on a resource object, whose lifetime is tied to the resulting observable sequence's lifetime.
|
188
|
+
def using(resource_factory, observable_factory)
|
189
|
+
AnonymousObservable.new do |observer|
|
190
|
+
source = nil
|
191
|
+
subscription = Subscription.empty
|
192
|
+
begin
|
193
|
+
resource = resource_factory.call
|
194
|
+
subscription = resource unless resource.nil?
|
195
|
+
source = observable_factory.call resource
|
196
|
+
rescue => e
|
197
|
+
next CompositeSubscription.new [self.raise_error(e).subscribe(observer), subscription]
|
198
|
+
end
|
199
|
+
|
200
|
+
CompositeSubscription.new [source.subscribe(observer), subscription]
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def from_array(array, scheduler = CurrentThreadScheduler.instance)
|
205
|
+
AnonymousObservable.new do |observer|
|
206
|
+
scheduler.schedule_recursive_with_state 0, lambda {|i, this|
|
207
|
+
if i < array.size
|
208
|
+
observer.on_next array[i]
|
209
|
+
this.call(i + 1)
|
210
|
+
else
|
211
|
+
observer.on_completed
|
212
|
+
end
|
213
|
+
}
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,735 @@
|
|
1
|
+
# Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
2
|
+
|
3
|
+
require 'monitor'
|
4
|
+
require 'rx_ruby/concurrency/async_lock'
|
5
|
+
require 'rx_ruby/subscriptions/subscription'
|
6
|
+
require 'rx_ruby/subscriptions/composite_subscription'
|
7
|
+
require 'rx_ruby/subscriptions/ref_count_subscription'
|
8
|
+
require 'rx_ruby/subscriptions/single_assignment_subscription'
|
9
|
+
require 'rx_ruby/core/observer'
|
10
|
+
require 'rx_ruby/core/observable'
|
11
|
+
require 'rx_ruby/operators/creation'
|
12
|
+
|
13
|
+
module RxRuby
|
14
|
+
|
15
|
+
module Observable
|
16
|
+
|
17
|
+
class AmbObserver
|
18
|
+
attr_accessor(:observer)
|
19
|
+
|
20
|
+
def method_missing(m, *args, &block)
|
21
|
+
@observer.method(m).call(*args)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Propagates the observable sequence that reacts first.
|
26
|
+
def amb(second)
|
27
|
+
AnonymousObservable.new do |observer|
|
28
|
+
left_subscription = SingleAssignmentSubscription.new
|
29
|
+
right_subscription = SingleAssignmentSubscription.new
|
30
|
+
choice = :neither
|
31
|
+
|
32
|
+
gate = Monitor.new
|
33
|
+
|
34
|
+
left = AmbObserver.new
|
35
|
+
right = AmbObserver.new
|
36
|
+
|
37
|
+
handle_left = lambda do |&action|
|
38
|
+
if choice == :neither
|
39
|
+
choice = :left
|
40
|
+
right_subscription.unsubscribe
|
41
|
+
left.observer = observer
|
42
|
+
end
|
43
|
+
|
44
|
+
action.call if choice == :left
|
45
|
+
end
|
46
|
+
|
47
|
+
handle_right = lambda do |&action|
|
48
|
+
if choice == :neither
|
49
|
+
choice = :right
|
50
|
+
left_subscription.unsubscribe
|
51
|
+
right.observer = observer
|
52
|
+
end
|
53
|
+
|
54
|
+
action.call if choice == :right
|
55
|
+
end
|
56
|
+
|
57
|
+
left_obs = Observer.configure do |o|
|
58
|
+
o.on_next {|x| handle_left.call { observer.on_next x } }
|
59
|
+
o.on_error {|err| handle_left.call { observer.on_error err } }
|
60
|
+
o.on_completed { handle_left.call { observer.on_completed } }
|
61
|
+
end
|
62
|
+
|
63
|
+
right_obs = Observer.configure do |o|
|
64
|
+
o.on_next {|x| handle_right.call { observer.on_next x } }
|
65
|
+
o.on_error {|err| handle_right.call { observer.on_error err } }
|
66
|
+
o.on_completed { handle_right.call { observer.on_completed } }
|
67
|
+
end
|
68
|
+
|
69
|
+
left.observer = Observer.allow_reentrancy(left_obs, gate)
|
70
|
+
right.observer = Observer.allow_reentrancy(right_obs, gate)
|
71
|
+
|
72
|
+
left_subscription.subscription = self.subscribe left
|
73
|
+
right_subscription.subscription = second.subscribe right
|
74
|
+
|
75
|
+
CompositeSubscription.new [left_subscription, right_subscription]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# Continues an observable sequence that is terminated by an exception of the specified type with the observable sequence produced by the handler or
|
80
|
+
# continues an observable sequence that is terminated by an exception with the next observable sequence.
|
81
|
+
def rescue_error(other = nil, &action)
|
82
|
+
return Observable.rescue_error(other) if other && !block_given?
|
83
|
+
raise ArgumentError.new 'Invalid arguments' if other.nil? && !block_given?
|
84
|
+
|
85
|
+
AnonymousObservable.new do |observer|
|
86
|
+
subscription = SerialSubscription.new
|
87
|
+
|
88
|
+
d1 = SingleAssignmentSubscription.new
|
89
|
+
subscription.subscription = d1
|
90
|
+
|
91
|
+
new_obs = Observer.configure do |o|
|
92
|
+
o.on_next(&observer.method(:on_next))
|
93
|
+
|
94
|
+
o.on_error do |err|
|
95
|
+
result = nil
|
96
|
+
begin
|
97
|
+
result = action.call(err)
|
98
|
+
rescue => e
|
99
|
+
observer.on_error(e)
|
100
|
+
next
|
101
|
+
end
|
102
|
+
|
103
|
+
d = SingleAssignmentSubscription.new
|
104
|
+
subscription.subscription = d
|
105
|
+
d.subscription = result.subscribe observer
|
106
|
+
end
|
107
|
+
|
108
|
+
o.on_completed(&observer.method(:on_completed))
|
109
|
+
end
|
110
|
+
|
111
|
+
d1.subscription = subscribe new_obs
|
112
|
+
subscription
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# Merges two observable sequences into one observable sequence by using the selector function whenever one of the observable sequences produces an element.
|
117
|
+
def combine_latest(other, &result_selector)
|
118
|
+
AnonymousObservable.new do |observer|
|
119
|
+
has_left = false
|
120
|
+
has_right = false
|
121
|
+
|
122
|
+
left = nil
|
123
|
+
right = nil
|
124
|
+
|
125
|
+
left_done = false
|
126
|
+
right_done = false
|
127
|
+
|
128
|
+
left_subscription = SingleAssignmentSubscription.new
|
129
|
+
right_subscription = SingleAssignmentSubscription.new
|
130
|
+
|
131
|
+
gate = Monitor.new
|
132
|
+
|
133
|
+
left_obs = Observer.configure do |o|
|
134
|
+
o.on_next do |l|
|
135
|
+
has_left = true
|
136
|
+
left = l
|
137
|
+
|
138
|
+
if has_right
|
139
|
+
res = nil
|
140
|
+
begin
|
141
|
+
res = result_selector.call left, right
|
142
|
+
rescue => e
|
143
|
+
observer.on_error e
|
144
|
+
return
|
145
|
+
end
|
146
|
+
observer.on_next res
|
147
|
+
end
|
148
|
+
|
149
|
+
observer.on_completed if right_done
|
150
|
+
end
|
151
|
+
|
152
|
+
o.on_error(&observer.method(:on_error))
|
153
|
+
|
154
|
+
o.on_completed do
|
155
|
+
left_done = true
|
156
|
+
observer.on_completed if right_done
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
right_obs = Observer.configure do |o|
|
161
|
+
o.on_next do |r|
|
162
|
+
has_right = true
|
163
|
+
right = r
|
164
|
+
|
165
|
+
if has_left
|
166
|
+
res = nil
|
167
|
+
begin
|
168
|
+
res = result_selector.call left, right
|
169
|
+
rescue => e
|
170
|
+
observer.on_error e
|
171
|
+
return
|
172
|
+
end
|
173
|
+
observer.on_next res
|
174
|
+
end
|
175
|
+
|
176
|
+
observer.on_completed if left_done
|
177
|
+
end
|
178
|
+
|
179
|
+
o.on_error(&observer.method(:on_error))
|
180
|
+
|
181
|
+
o.on_completed do
|
182
|
+
right_done = true
|
183
|
+
observer.on_completed if left_done
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
left_subscription.subscription = synchronize(gate).subscribe(left_obs)
|
188
|
+
right_subscription.subscription = other.synchronize(gate).subscribe(right_obs)
|
189
|
+
|
190
|
+
CompositeSubscription.new [left_subscription, right_subscription]
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Concatenates the second observable sequence to the first observable sequence upon successful termination of the first.
|
195
|
+
def concat(*other)
|
196
|
+
Observable.concat([self, *other].to_enum)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Merges elements from two observable sequences into a single observable sequence, using the specified scheduler for enumeration of and subscription to the sources.
|
200
|
+
def merge(other, scheduler = CurrentThreadScheduler.instance)
|
201
|
+
Observable.merge_all(scheduler, *[self, other])
|
202
|
+
end
|
203
|
+
|
204
|
+
# Merges elements from all inner observable sequences into a single observable sequence, limiting the number of concurrent subscriptions to inner sequences.
|
205
|
+
def merge_concurrent(max_concurrent = 1)
|
206
|
+
AnonymousObservable.new do |observer|
|
207
|
+
gate = Monitor.new
|
208
|
+
q = []
|
209
|
+
stopped = false
|
210
|
+
group = CompositeSubscription.new
|
211
|
+
active = 0
|
212
|
+
|
213
|
+
subscriber = nil
|
214
|
+
subscriber = lambda do |xs|
|
215
|
+
subscription = SingleAssignmentSubscription.new
|
216
|
+
group << subscription
|
217
|
+
|
218
|
+
new_obs = Observer.configure do |o|
|
219
|
+
o.on_next {|x| gate.synchronize { observer.on_next x } }
|
220
|
+
|
221
|
+
o.on_error {|err| gate.synchronize { observer.on_error err } }
|
222
|
+
|
223
|
+
o.on_completed do
|
224
|
+
group.delete subscription
|
225
|
+
gate.synchronize do
|
226
|
+
if q.length > 0
|
227
|
+
s = q.shift
|
228
|
+
subscriber.call s
|
229
|
+
else
|
230
|
+
active -= 1
|
231
|
+
observer.on_completed if stopped && active == 0
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
xs.subscribe new_obs
|
238
|
+
end
|
239
|
+
|
240
|
+
inner_obs = Observer.configure do |o|
|
241
|
+
o.on_next do |inner_source|
|
242
|
+
gate.synchronize do
|
243
|
+
if active < max_concurrent
|
244
|
+
active += 1
|
245
|
+
subscriber.call inner_source
|
246
|
+
else
|
247
|
+
q << inner_source
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
o.on_error {|err| gate.synchronize { observer.on_error err } }
|
253
|
+
|
254
|
+
o.on_completed do
|
255
|
+
stopped = true
|
256
|
+
observer.on_completed if active == 0
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
group << subscribe(inner_obs)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Concatenates all inner observable sequences, as long as the previous observable sequence terminated successfully.
|
265
|
+
def merge_all
|
266
|
+
AnonymousObservable.new do |observer|
|
267
|
+
gate = Monitor.new
|
268
|
+
stopped = false
|
269
|
+
m = SingleAssignmentSubscription.new
|
270
|
+
group = CompositeSubscription.new [m]
|
271
|
+
|
272
|
+
new_obs = Observer.configure do |o|
|
273
|
+
o.on_next do |inner_source|
|
274
|
+
inner_subscription = SingleAssignmentSubscription.new
|
275
|
+
group << inner_subscription
|
276
|
+
|
277
|
+
inner_obs = Observer.configure do |io|
|
278
|
+
io.on_next {|x| gate.synchronize { observer.on_next x } }
|
279
|
+
|
280
|
+
io.on_error {|err| gate.synchronize { observer.on_error err } }
|
281
|
+
|
282
|
+
io.on_completed do
|
283
|
+
group.delete inner_subscription
|
284
|
+
gate.synchronize { observer.on_completed } if stopped && group.length == 1
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
inner_subscription.subscription = inner_source.subscribe inner_obs
|
289
|
+
end
|
290
|
+
|
291
|
+
o.on_error {|err| gate.synchronize { observer.on_error err } }
|
292
|
+
|
293
|
+
o.on_completed do
|
294
|
+
stopped = true
|
295
|
+
gate.synchronize { observer.on_completed } if group.length == 1
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
subscribe new_obs
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
# Concatenates the second observable sequence to the first observable sequence upon successful or exceptional termination of the first.
|
304
|
+
def on_error_resume_next(other)
|
305
|
+
raise ArgumentError.new 'Other cannot be nil' unless other
|
306
|
+
|
307
|
+
Observable.on_error_resume_next self, other
|
308
|
+
end
|
309
|
+
|
310
|
+
# Returns the elements from the source observable sequence only after the other observable sequence produces an element.
|
311
|
+
def skip_until(other)
|
312
|
+
raise ArgumentError.new 'Other cannot be nil' unless other
|
313
|
+
|
314
|
+
AnonymousObservable.new do |observer|
|
315
|
+
source_subscription = SingleAssignmentSubscription.new
|
316
|
+
other_subscription = SingleAssignmentSubscription.new
|
317
|
+
|
318
|
+
open = false
|
319
|
+
gate = Monitor.new
|
320
|
+
|
321
|
+
source_obs = Observer.configure do |o|
|
322
|
+
o.on_next {|x| observer.on_next x if open }
|
323
|
+
o.on_error(&observer.method(:on_error))
|
324
|
+
o.on_completed { observer.on_completed if open }
|
325
|
+
end
|
326
|
+
|
327
|
+
other_obs = Observer.configure do |o|
|
328
|
+
o.on_next do |_|
|
329
|
+
open = true
|
330
|
+
other_subscription.unsubscribe
|
331
|
+
end
|
332
|
+
|
333
|
+
o.on_error(&observer.method(:on_error))
|
334
|
+
end
|
335
|
+
|
336
|
+
source_subscription.subscription = synchronize(gate).subscribe(source_obs)
|
337
|
+
other_subscription.subscription = other.synchronize(gate).subscribe(other_obs)
|
338
|
+
|
339
|
+
CompositeSubscription.new [source_subscription, other_subscription]
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# Transforms an observable sequence of observable sequences into an observable sequence
|
344
|
+
# producing values only from the most recent observable sequence.
|
345
|
+
# Each time a new inner observable sequence is received, unsubscribe from the
|
346
|
+
# previous inner observable sequence.
|
347
|
+
def latest
|
348
|
+
AnonymousObservable.new do |observer|
|
349
|
+
gate = Monitor.new
|
350
|
+
inner_subscription = SerialSubscription.new
|
351
|
+
stopped = false
|
352
|
+
latest_num = 0
|
353
|
+
has_latest = false
|
354
|
+
|
355
|
+
source_obs = Observer.configure do |o|
|
356
|
+
o.on_next do |inner_source|
|
357
|
+
id = 0
|
358
|
+
|
359
|
+
gate.synchronize do
|
360
|
+
latest_num += 1
|
361
|
+
id = latest_num
|
362
|
+
end
|
363
|
+
|
364
|
+
d = SingleAssignmentSubscription.new
|
365
|
+
|
366
|
+
inner_obs = Observer.configure do |io|
|
367
|
+
io.on_next {|x| gate.synchronize { observer.on_next x if latest_num == id } }
|
368
|
+
io.on_error do |err|
|
369
|
+
gate.synchronize do
|
370
|
+
has_latest = false
|
371
|
+
observer.on_error err if latest_num == id
|
372
|
+
end
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
d.subscription = inner_source.subscribe inner_obs
|
377
|
+
end
|
378
|
+
|
379
|
+
o.on_error {|err| gate.synchronize { observer.on_error err } }
|
380
|
+
|
381
|
+
o.on_completed do
|
382
|
+
gate.synchronize do
|
383
|
+
stopped = true
|
384
|
+
observer.on_completed unless has_latest
|
385
|
+
end
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
subscription = subscribe source_obs
|
390
|
+
|
391
|
+
CompositeSubscription.new [subscription, inner_subscription]
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
# Returns the elements from the source observable sequence until the other observable sequence produces an element.
|
396
|
+
def take_until(other)
|
397
|
+
raise ArgumentError.new 'other cannot be nil' unless other
|
398
|
+
|
399
|
+
AnonymousObservable.new do |observer|
|
400
|
+
source_subscription = SingleAssignmentSubscription.new
|
401
|
+
other_subscription = SingleAssignmentSubscription.new
|
402
|
+
|
403
|
+
gate = Monitor.new
|
404
|
+
|
405
|
+
other_obs = Observer.configure do |o|
|
406
|
+
o.on_next {|_| observer.on_completed }
|
407
|
+
o.on_error(&observer.method(:on_error))
|
408
|
+
end
|
409
|
+
|
410
|
+
other_subscription.subscription = other.synchronize(gate).subscribe(other_obs)
|
411
|
+
source_subscription.subscription = synchronize(gate).ensures(&other_subscription.method(:unsubscribe)).subscribe(observer)
|
412
|
+
|
413
|
+
CompositeSubscription.new [source_subscription, other_subscription]
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
# Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index.
|
418
|
+
def zip(*args, &result_selector)
|
419
|
+
args.unshift(self)
|
420
|
+
Observable.zip(*args, &result_selector)
|
421
|
+
end
|
422
|
+
|
423
|
+
class << self
|
424
|
+
|
425
|
+
# Propagates the observable sequence that reacts first.
|
426
|
+
def amb(*args)
|
427
|
+
args.reduce(Observable.never) {|previous, current| previous.amb current }
|
428
|
+
end
|
429
|
+
|
430
|
+
# Continues an observable sequence that is terminated by an exception with the next observable sequence.
|
431
|
+
def rescue_error(*args)
|
432
|
+
AnonymousObservable.new do |observer|
|
433
|
+
gate = AsyncLock.new
|
434
|
+
disposed = false
|
435
|
+
e = args.length == 1 && args[0].is_a?(Enumerator) ? args[0] : args.to_enum
|
436
|
+
subscription = SerialSubscription.new
|
437
|
+
last_error = nil
|
438
|
+
|
439
|
+
cancelable = CurrentThreadScheduler.instance.schedule_recursive lambda {|this|
|
440
|
+
gate.wait do
|
441
|
+
current = nil
|
442
|
+
has_next = false
|
443
|
+
error = nil
|
444
|
+
|
445
|
+
if disposed
|
446
|
+
return
|
447
|
+
else
|
448
|
+
begin
|
449
|
+
current = e.next
|
450
|
+
has_next = true
|
451
|
+
rescue StopIteration => _
|
452
|
+
# Do nothing
|
453
|
+
rescue => e
|
454
|
+
error = e
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
if error
|
459
|
+
observer.on_error error
|
460
|
+
return
|
461
|
+
end
|
462
|
+
|
463
|
+
unless has_next
|
464
|
+
if last_error
|
465
|
+
observer.on_error last_error
|
466
|
+
else
|
467
|
+
observer.on_completed
|
468
|
+
end
|
469
|
+
return
|
470
|
+
end
|
471
|
+
|
472
|
+
new_obs = Observer.configure do |o|
|
473
|
+
o.on_next(&observer.method(:on_next))
|
474
|
+
|
475
|
+
o.on_error do |err|
|
476
|
+
last_error = err
|
477
|
+
this.call
|
478
|
+
end
|
479
|
+
|
480
|
+
o.on_completed(&observer.method(:on_completed))
|
481
|
+
end
|
482
|
+
|
483
|
+
d = SingleAssignmentSubscription.new
|
484
|
+
subscription.subscription = d
|
485
|
+
d.subscription = current.subscribe new_obs
|
486
|
+
end
|
487
|
+
}
|
488
|
+
|
489
|
+
CompositeSubscription.new [subscription, cancelable, Subscription.create { gate.wait { disposed = true } }]
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
# Merges the specified observable sequences into one observable sequence by using the selector function whenever any of the observable sequences produces an element.
|
494
|
+
def combine_latest(*args, &result_selector)
|
495
|
+
AnonymousObservable.new do |observer|
|
496
|
+
result_selector ||= lambda {|*inner_args| inner_args }
|
497
|
+
|
498
|
+
n = args.length
|
499
|
+
has_value = Array.new(n, false)
|
500
|
+
has_value_all = false
|
501
|
+
|
502
|
+
values = Array.new(n)
|
503
|
+
is_done = Array.new(n, false)
|
504
|
+
|
505
|
+
next_item = lambda do |i|
|
506
|
+
has_value[i] = true
|
507
|
+
if has_value_all || (has_value_all = has_value.all?)
|
508
|
+
res = nil
|
509
|
+
begin
|
510
|
+
res = result_selector.call(*values)
|
511
|
+
rescue => e
|
512
|
+
observer.on_error e
|
513
|
+
return
|
514
|
+
end
|
515
|
+
|
516
|
+
observer.on_next(res)
|
517
|
+
elsif enumerable_select_with_index(is_done) {|_, j| j != i} .all?
|
518
|
+
observer.on_completed
|
519
|
+
return
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
done = lambda do |i|
|
524
|
+
is_done[i] = true
|
525
|
+
observer.on_completed if is_done.all?
|
526
|
+
end
|
527
|
+
|
528
|
+
gate = Monitor.new
|
529
|
+
subscriptions = Array.new(n) do |i|
|
530
|
+
sas = SingleAssignmentSubscription.new
|
531
|
+
|
532
|
+
sas_obs = Observer.configure do |o|
|
533
|
+
o.on_next do |x|
|
534
|
+
values[i] = x
|
535
|
+
next_item.call i
|
536
|
+
end
|
537
|
+
|
538
|
+
o.on_error(&observer.method(:on_error))
|
539
|
+
|
540
|
+
o.on_completed { done.call i }
|
541
|
+
end
|
542
|
+
|
543
|
+
sas.subscription = args[i].synchronize(gate).subscribe(sas_obs)
|
544
|
+
|
545
|
+
sas
|
546
|
+
end
|
547
|
+
|
548
|
+
CompositeSubscription.new subscriptions
|
549
|
+
end
|
550
|
+
end
|
551
|
+
|
552
|
+
# Concatenates all of the specified observable sequences, as long as the previous observable sequence terminated successfully.
|
553
|
+
def concat(*args)
|
554
|
+
AnonymousObservable.new do |observer|
|
555
|
+
disposed = false
|
556
|
+
e = args.length == 1 && args[0].is_a?(Enumerator) ? args[0] : args.to_enum
|
557
|
+
subscription = SerialSubscription.new
|
558
|
+
gate = AsyncLock.new
|
559
|
+
|
560
|
+
cancelable = CurrentThreadScheduler.instance.schedule_recursive lambda {|this|
|
561
|
+
gate.wait do
|
562
|
+
current = nil
|
563
|
+
has_next = false
|
564
|
+
err = nil
|
565
|
+
|
566
|
+
if disposed
|
567
|
+
return
|
568
|
+
else
|
569
|
+
begin
|
570
|
+
current = e.next
|
571
|
+
has_next = true
|
572
|
+
rescue StopIteration => _
|
573
|
+
# Do nothing
|
574
|
+
rescue => e
|
575
|
+
err = e
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
if err
|
580
|
+
observer.on_error err
|
581
|
+
return
|
582
|
+
end
|
583
|
+
|
584
|
+
unless has_next
|
585
|
+
observer.on_completed
|
586
|
+
return
|
587
|
+
end
|
588
|
+
|
589
|
+
d = SingleAssignmentSubscription.new
|
590
|
+
subscription.subscription = d
|
591
|
+
|
592
|
+
new_obs = Observer.configure do |o|
|
593
|
+
o.on_next(&observer.method(:on_next))
|
594
|
+
o.on_error(&observer.method(:on_error))
|
595
|
+
o.on_completed { this.call }
|
596
|
+
end
|
597
|
+
|
598
|
+
current.subscribe new_obs
|
599
|
+
end
|
600
|
+
}
|
601
|
+
|
602
|
+
CompositeSubscription.new [subscription, cancelable, Subscription.create { gate.wait { disposed = true }}]
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
# Merges elements from all observable sequences in the given enumerable sequence into a single observable sequence, limiting the number of concurrent subscriptions to inner sequences, and using the specified scheduler for enumeration of and subscription to the sources.
|
607
|
+
def merge_concurrent(max_concurrent, scheduler = CurrentThreadScheduler.instance, *args)
|
608
|
+
Observable.from_array(args, scheduler).merge_concurrent(max_concurrent)
|
609
|
+
end
|
610
|
+
|
611
|
+
# Merges elements from all of the specified observable sequences into a single observable sequence, using the specified scheduler for enumeration of and subscription to the sources.
|
612
|
+
def merge_all(*args)
|
613
|
+
scheduler = CurrentThreadScheduler.instance
|
614
|
+
if args.size > 0 && Scheduler === args[0]
|
615
|
+
scheduler = args.shift
|
616
|
+
end
|
617
|
+
Observable.from_array(args, scheduler).merge_all
|
618
|
+
end
|
619
|
+
alias :merge :merge_all
|
620
|
+
|
621
|
+
# Concatenates all of the specified observable sequences, even if the previous observable sequence terminated exceptionally.
|
622
|
+
def on_error_resume_next(*args)
|
623
|
+
AnonymousObservable.new do |observer|
|
624
|
+
gate = AsyncLock.new
|
625
|
+
disposed = false
|
626
|
+
e = args.length == 1 && args[0].is_a?(Enumerator) ? args[0] : args.to_enum
|
627
|
+
subscription = SerialSubscription.new
|
628
|
+
|
629
|
+
cancelable = CurrentThreadScheduler.instance.schedule_recursive lambda {|this|
|
630
|
+
gate.wait do
|
631
|
+
current = nil
|
632
|
+
has_next = false
|
633
|
+
err = nil
|
634
|
+
|
635
|
+
if !disposed
|
636
|
+
begin
|
637
|
+
current = e.next
|
638
|
+
has_next = true
|
639
|
+
rescue StopIteration => _
|
640
|
+
# Do nothing
|
641
|
+
rescue => e
|
642
|
+
err = e
|
643
|
+
end
|
644
|
+
else
|
645
|
+
return
|
646
|
+
end
|
647
|
+
|
648
|
+
if err
|
649
|
+
observer.on_error err
|
650
|
+
return
|
651
|
+
end
|
652
|
+
|
653
|
+
unless has_next
|
654
|
+
observer.on_completed
|
655
|
+
return
|
656
|
+
end
|
657
|
+
|
658
|
+
d = SingleAssignmentSubscription.new
|
659
|
+
subscription.subscription = d
|
660
|
+
|
661
|
+
new_obs = Observer.configure do |o|
|
662
|
+
o.on_next(&observer.method(:on_next))
|
663
|
+
o.on_error {|_| this.call }
|
664
|
+
o.on_completed { this.call }
|
665
|
+
end
|
666
|
+
|
667
|
+
d.subscription = current.subscribe new_obs
|
668
|
+
end
|
669
|
+
}
|
670
|
+
|
671
|
+
CompositeSubscription.new [subscription, cancelable, Subscription.create { gate.wait { disposed = true } }]
|
672
|
+
end
|
673
|
+
end
|
674
|
+
|
675
|
+
# Merges the specified observable sequences into one observable sequence by using the selector function whenever all of the observable sequences have produced an element at a corresponding index.
|
676
|
+
def zip(*args, &result_selector)
|
677
|
+
AnonymousObservable.new do |observer|
|
678
|
+
result_selector ||= lambda {|*inner_args| inner_args }
|
679
|
+
n = args.length
|
680
|
+
|
681
|
+
queues = Array.new(n) {|i| Array.new }
|
682
|
+
is_done = Array.new(n, false)
|
683
|
+
|
684
|
+
next_action = lambda do |i|
|
685
|
+
if queues.all? {|q| q.length > 0 }
|
686
|
+
res = queues.map {|q| q.shift }
|
687
|
+
observer.on_next(result_selector.call(*res))
|
688
|
+
elsif enumerable_select_with_index(is_done) {|x, j| j != i } .all?
|
689
|
+
observer.on_completed
|
690
|
+
end
|
691
|
+
end
|
692
|
+
|
693
|
+
done = lambda do |i|
|
694
|
+
is_done[i] = true
|
695
|
+
observer.on_completed if is_done.all?
|
696
|
+
end
|
697
|
+
|
698
|
+
gate = Monitor.new
|
699
|
+
|
700
|
+
subscriptions = Array.new(n) do |i|
|
701
|
+
sas = SingleAssignmentSubscription.new
|
702
|
+
|
703
|
+
sas_obs = Observer.configure do |o|
|
704
|
+
o.on_next do |x|
|
705
|
+
queues[i].push(x)
|
706
|
+
next_action.call i
|
707
|
+
end
|
708
|
+
|
709
|
+
o.on_error(&observer.method(:on_error))
|
710
|
+
|
711
|
+
o.on_completed { done.call i }
|
712
|
+
end
|
713
|
+
|
714
|
+
sas.subscription = args[i].synchronize(gate).subscribe(sas_obs)
|
715
|
+
sas
|
716
|
+
end
|
717
|
+
|
718
|
+
subscriptions.push(Subscription.create { queues.each {|q| q = [] }})
|
719
|
+
|
720
|
+
CompositeSubscription.new subscriptions
|
721
|
+
end
|
722
|
+
end
|
723
|
+
|
724
|
+
private
|
725
|
+
|
726
|
+
def enumerable_select_with_index(arr, &block)
|
727
|
+
[].tap do |new_arr|
|
728
|
+
arr.each_with_index do |item, index|
|
729
|
+
new_arr.push item if block.call item, index
|
730
|
+
end
|
731
|
+
end
|
732
|
+
end
|
733
|
+
end
|
734
|
+
end
|
735
|
+
end
|