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.
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