rx_ruby 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +7 -0
  2. data/.gitattributes +22 -0
  3. data/.gitignore +173 -0
  4. data/.travis.yml +10 -0
  5. data/Gemfile +4 -0
  6. data/Rakefile +11 -0
  7. data/examples/aggregate.rb +39 -0
  8. data/examples/amb.rb +25 -0
  9. data/examples/ambproto.rb +24 -0
  10. data/examples/and.rb +26 -0
  11. data/examples/as_observable.rb +25 -0
  12. data/examples/average.rb +43 -0
  13. data/examples/buffer_with_count.rb +44 -0
  14. data/examples/buffer_with_time.rb +51 -0
  15. data/examples/case.rb +29 -0
  16. data/examples/catch.rb +20 -0
  17. data/examples/catchproto.rb +39 -0
  18. data/examples/combine_latest.rb +35 -0
  19. data/examples/combine_latestproto.rb +33 -0
  20. data/examples/concat.rb +22 -0
  21. data/examples/concat_all.rb +27 -0
  22. data/examples/concat_map.rb +61 -0
  23. data/examples/concat_map_observer.rb +29 -0
  24. data/examples/concatproto.rb +25 -0
  25. data/examples/connect.rb +41 -0
  26. data/examples/contains.rb +37 -0
  27. data/examples/count.rb +36 -0
  28. data/examples/create.rb +55 -0
  29. data/examples/debounce.rb +35 -0
  30. data/examples/default_if_empty.rb +35 -0
  31. data/examples/defer.rb +20 -0
  32. data/examples/delay.rb +49 -0
  33. data/examples/delay_with_selector.rb +63 -0
  34. data/examples/dematerialize.rb +22 -0
  35. data/examples/disposable.rb +12 -0
  36. data/examples/distinct.rb +43 -0
  37. data/examples/distinct_until_changed.rb +43 -0
  38. data/examples/do.rb +59 -0
  39. data/examples/empty.rb +16 -0
  40. data/examples/for.rb +26 -0
  41. data/examples/fork_join.rb +23 -0
  42. data/examples/from.rb +106 -0
  43. data/examples/from_array.rb +21 -0
  44. data/examples/from_callback.rb +21 -0
  45. data/examples/generate.rb +24 -0
  46. data/examples/group_join.rb +39 -0
  47. data/examples/if.rb +46 -0
  48. data/examples/intervals.rb +26 -0
  49. data/examples/merge.rb +36 -0
  50. data/examples/merge_all.rb +27 -0
  51. data/examples/multicast.rb +32 -0
  52. data/examples/never.rb +15 -0
  53. data/examples/of.rb +19 -0
  54. data/examples/on_error_resume_next.rb +21 -0
  55. data/examples/pairs.rb +26 -0
  56. data/examples/publish.rb +79 -0
  57. data/examples/range.rb +19 -0
  58. data/examples/reduce.rb +18 -0
  59. data/examples/repeat.rb +19 -0
  60. data/examples/return.rb +17 -0
  61. data/examples/scan.rb +41 -0
  62. data/examples/start.rb +29 -0
  63. data/examples/throw.rb +17 -0
  64. data/examples/time_intervals.rb +28 -0
  65. data/examples/timer.rb +26 -0
  66. data/examples/timestamp.rb +28 -0
  67. data/examples/to_a.rb +23 -0
  68. data/examples/to_async.rb +26 -0
  69. data/examples/using.rb +52 -0
  70. data/examples/when.rb +26 -0
  71. data/examples/while.rb +25 -0
  72. data/examples/window_with_time.rb +78 -0
  73. data/examples/zip.rb +27 -0
  74. data/examples/zip_array.rb +25 -0
  75. data/lib/core_ext/enumerable.rb +22 -0
  76. data/lib/rx_ruby.rb +27 -0
  77. data/lib/rx_ruby/concurrency/async_lock.rb +57 -0
  78. data/lib/rx_ruby/concurrency/current_thread_scheduler.rb +75 -0
  79. data/lib/rx_ruby/concurrency/default_scheduler.rb +51 -0
  80. data/lib/rx_ruby/concurrency/historical_scheduler.rb +16 -0
  81. data/lib/rx_ruby/concurrency/immediate_scheduler.rb +68 -0
  82. data/lib/rx_ruby/concurrency/local_scheduler.rb +39 -0
  83. data/lib/rx_ruby/concurrency/periodic_scheduler.rb +74 -0
  84. data/lib/rx_ruby/concurrency/scheduled_item.rb +42 -0
  85. data/lib/rx_ruby/concurrency/scheduler.rb +150 -0
  86. data/lib/rx_ruby/concurrency/virtual_time_scheduler.rb +170 -0
  87. data/lib/rx_ruby/core/async_lock_observer.rb +46 -0
  88. data/lib/rx_ruby/core/auto_detach_observer.rb +59 -0
  89. data/lib/rx_ruby/core/checked_observer.rb +66 -0
  90. data/lib/rx_ruby/core/notification.rb +161 -0
  91. data/lib/rx_ruby/core/observable.rb +104 -0
  92. data/lib/rx_ruby/core/observe_on_observer.rb +50 -0
  93. data/lib/rx_ruby/core/observer.rb +119 -0
  94. data/lib/rx_ruby/core/scheduled_observer.rb +83 -0
  95. data/lib/rx_ruby/core/synchronized_observer.rb +47 -0
  96. data/lib/rx_ruby/core/time_interval.rb +17 -0
  97. data/lib/rx_ruby/internal/priority_queue.rb +122 -0
  98. data/lib/rx_ruby/internal/util.rb +9 -0
  99. data/lib/rx_ruby/joins/active_plan.rb +45 -0
  100. data/lib/rx_ruby/joins/join_observer.rb +51 -0
  101. data/lib/rx_ruby/joins/pattern.rb +14 -0
  102. data/lib/rx_ruby/joins/plan.rb +44 -0
  103. data/lib/rx_ruby/linq/connectable_observable.rb +34 -0
  104. data/lib/rx_ruby/linq/observable/_observable_timer_date_and_period.rb +22 -0
  105. data/lib/rx_ruby/linq/observable/_observable_timer_time_span.rb +14 -0
  106. data/lib/rx_ruby/linq/observable/_observable_timer_time_span_and_period.rb +20 -0
  107. data/lib/rx_ruby/linq/observable/aggregate.rb +7 -0
  108. data/lib/rx_ruby/linq/observable/and.rb +7 -0
  109. data/lib/rx_ruby/linq/observable/case.rb +15 -0
  110. data/lib/rx_ruby/linq/observable/concat_all.rb +7 -0
  111. data/lib/rx_ruby/linq/observable/concat_map.rb +35 -0
  112. data/lib/rx_ruby/linq/observable/concat_map_observer.rb +43 -0
  113. data/lib/rx_ruby/linq/observable/contains.rb +28 -0
  114. data/lib/rx_ruby/linq/observable/debounce.rb +41 -0
  115. data/lib/rx_ruby/linq/observable/delay.rb +81 -0
  116. data/lib/rx_ruby/linq/observable/delay_with_selector.rb +64 -0
  117. data/lib/rx_ruby/linq/observable/do.rb +42 -0
  118. data/lib/rx_ruby/linq/observable/for.rb +13 -0
  119. data/lib/rx_ruby/linq/observable/fork_join.rb +55 -0
  120. data/lib/rx_ruby/linq/observable/from.rb +34 -0
  121. data/lib/rx_ruby/linq/observable/group_join.rb +108 -0
  122. data/lib/rx_ruby/linq/observable/if.rb +17 -0
  123. data/lib/rx_ruby/linq/observable/interval.rb +5 -0
  124. data/lib/rx_ruby/linq/observable/multicast.rb +14 -0
  125. data/lib/rx_ruby/linq/observable/of.rb +11 -0
  126. data/lib/rx_ruby/linq/observable/pairs.rb +7 -0
  127. data/lib/rx_ruby/linq/observable/pluck.rb +7 -0
  128. data/lib/rx_ruby/linq/observable/publish.rb +11 -0
  129. data/lib/rx_ruby/linq/observable/start.rb +7 -0
  130. data/lib/rx_ruby/linq/observable/time_interval.rb +15 -0
  131. data/lib/rx_ruby/linq/observable/timer.rb +26 -0
  132. data/lib/rx_ruby/linq/observable/timestamp.rb +9 -0
  133. data/lib/rx_ruby/linq/observable/to_async.rb +40 -0
  134. data/lib/rx_ruby/linq/observable/when.rb +36 -0
  135. data/lib/rx_ruby/linq/observable/while.rb +41 -0
  136. data/lib/rx_ruby/operators/aggregates.rb +611 -0
  137. data/lib/rx_ruby/operators/creation.rb +220 -0
  138. data/lib/rx_ruby/operators/multiple.rb +735 -0
  139. data/lib/rx_ruby/operators/single.rb +399 -0
  140. data/lib/rx_ruby/operators/standard_query_operators.rb +279 -0
  141. data/lib/rx_ruby/operators/synchronization.rb +47 -0
  142. data/lib/rx_ruby/operators/time.rb +120 -0
  143. data/lib/rx_ruby/subjects/async_subject.rb +161 -0
  144. data/lib/rx_ruby/subjects/behavior_subject.rb +149 -0
  145. data/lib/rx_ruby/subjects/replay_subject.rb +39 -0
  146. data/lib/rx_ruby/subjects/subject.rb +131 -0
  147. data/lib/rx_ruby/subjects/subject_extensions.rb +45 -0
  148. data/lib/rx_ruby/subscriptions/composite_subscription.rb +91 -0
  149. data/lib/rx_ruby/subscriptions/ref_count_subscription.rb +88 -0
  150. data/lib/rx_ruby/subscriptions/scheduled_subscription.rb +32 -0
  151. data/lib/rx_ruby/subscriptions/serial_subscription.rb +60 -0
  152. data/lib/rx_ruby/subscriptions/single_assignment_subscription.rb +64 -0
  153. data/lib/rx_ruby/subscriptions/subscription.rb +56 -0
  154. data/lib/rx_ruby/testing/cold_observable.rb +45 -0
  155. data/lib/rx_ruby/testing/hot_observable.rb +47 -0
  156. data/lib/rx_ruby/testing/mock_observer.rb +33 -0
  157. data/lib/rx_ruby/testing/reactive_test.rb +94 -0
  158. data/lib/rx_ruby/testing/recorded.rb +17 -0
  159. data/lib/rx_ruby/testing/test_scheduler.rb +96 -0
  160. data/lib/rx_ruby/testing/test_subscription.rb +22 -0
  161. data/lib/rx_ruby/version.rb +3 -0
  162. data/license.txt +13 -0
  163. data/readme.md +152 -0
  164. data/rx_ruby.gemspec +22 -0
  165. data/test/rx_ruby/concurrency/helpers/historical_virtual_scheduler_helper.rb +135 -0
  166. data/test/rx_ruby/concurrency/helpers/immediate_local_scheduler_helper.rb +51 -0
  167. data/test/rx_ruby/concurrency/test_async_lock.rb +56 -0
  168. data/test/rx_ruby/concurrency/test_current_thread_scheduler.rb +44 -0
  169. data/test/rx_ruby/concurrency/test_default_scheduler.rb +44 -0
  170. data/test/rx_ruby/concurrency/test_historical_scheduler.rb +18 -0
  171. data/test/rx_ruby/concurrency/test_immediate_scheduler.rb +53 -0
  172. data/test/rx_ruby/concurrency/test_local_scheduler.rb +12 -0
  173. data/test/rx_ruby/concurrency/test_periodic_scheduler.rb +53 -0
  174. data/test/rx_ruby/concurrency/test_scheduled_item.rb +50 -0
  175. data/test/rx_ruby/concurrency/test_scheduler.rb +128 -0
  176. data/test/rx_ruby/concurrency/test_virtual_time_scheduler.rb +14 -0
  177. data/test/rx_ruby/core/test_notification.rb +129 -0
  178. data/test/rx_ruby/core/test_observable_creation.rb +483 -0
  179. data/test/rx_ruby/core/test_observer.rb +634 -0
  180. data/test/rx_ruby/internal/test_priority_queue.rb +71 -0
  181. data/test/rx_ruby/subscriptions/test_composite_subscription.rb +116 -0
  182. data/test/rx_ruby/subscriptions/test_serial_subscription.rb +62 -0
  183. data/test/rx_ruby/subscriptions/test_singleassignment_subscription.rb +61 -0
  184. data/test/rx_ruby/subscriptions/test_subscription.rb +27 -0
  185. data/test/test_helper.rb +11 -0
  186. 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