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,47 @@
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/subscriptions/single_assignment_subscription'
5
+ require 'rx_ruby/subscriptions/serial_subscription'
6
+ require 'rx_ruby/subscriptions/scheduled_subscription'
7
+ require 'rx_ruby/core/observer'
8
+ require 'rx_ruby/core/observable'
9
+ require 'rx_ruby/core/observe_on_observer'
10
+
11
+ module RxRuby
12
+ module Observable
13
+
14
+ # Wraps the source sequence in order to run its subscription and unsubscribe logic on the specified scheduler.
15
+ def subscribe_on(scheduler)
16
+ raise ArgumentError.new 'Scheduler cannot be nil' unless scheduler
17
+
18
+ AnonymousObservable.new do |observer|
19
+ m = SingleAssignmentSubscription.new
20
+ d = SerialSubscription.new
21
+ d.subscription = m
22
+
23
+ m.subscription = scheduler.schedule lambda {
24
+ d.subscription = ScheduledSubscription.new scheduler, (subscribe observer)
25
+ }
26
+
27
+ d
28
+ end
29
+ end
30
+
31
+ # Wraps the source sequence in order to run its observer callbacks on the specified scheduler.
32
+ def observe_on(scheduler)
33
+ raise ArgumentError.new 'Scheduler cannot be nil' unless scheduler
34
+
35
+ AnonymousObservable.new do |observer|
36
+ subscribe(ObserveOnObserver.new scheduler, observer)
37
+ end
38
+ end
39
+
40
+ # Wraps the source sequence in order to ensure observer callbacks are synchronized using the specified gate object.
41
+ def synchronize(gate = Monitor.new)
42
+ AnonymousObservable.new do |observer|
43
+ subscribe(Observer.allow_reentrancy observer, gate)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,120 @@
1
+ # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
2
+
3
+ require 'thread'
4
+ require 'rx_ruby/concurrency/default_scheduler'
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/serial_subscription'
9
+ require 'rx_ruby/subscriptions/single_assignment_subscription'
10
+ require 'rx_ruby/core/observer'
11
+ require 'rx_ruby/core/observable'
12
+ require 'rx_ruby/subjects/subject'
13
+
14
+
15
+ module RxRuby
16
+
17
+ # Time based operations
18
+ module Observable
19
+
20
+ # Projects each element of an observable sequence into consecutive non-overlapping buffers which are produced
21
+ # based on timing information.
22
+ def buffer_with_time(time_span, time_shift = time_span, scheduler = DefaultScheduler.instance)
23
+ raise ArgumentError.new 'time_span must be greater than zero' if time_span <= 0
24
+ raise ArgumentError.new 'time_span must be greater than zero' if time_shift <= 0
25
+ window_with_time(time_span, time_shift, scheduler).flat_map(&:to_a)
26
+ end
27
+
28
+ # Projects each element of an observable sequence into consecutive non-overlapping windows which are produced
29
+ # based on timing information.
30
+ def window_with_time(time_span, time_shift = time_span, scheduler = DefaultScheduler.instance)
31
+ raise ArgumentError.new 'time_span must be greater than zero' if time_span <= 0
32
+ raise ArgumentError.new 'time_span must be greater than zero' if time_shift <= 0
33
+
34
+ AnonymousObservable.new do |observer|
35
+ total_time = 0
36
+ next_shift = time_shift
37
+ next_span = time_span
38
+
39
+ gate = Mutex.new
40
+ q = []
41
+
42
+ timer_d = SerialSubscription.new
43
+ group_subscription = CompositeSubscription.new [timer_d]
44
+ ref_count_subscription = RefCountSubscription.new(group_subscription)
45
+
46
+ create_timer = lambda {
47
+ m = SingleAssignmentSubscription.new
48
+ timer_d.subscription = m
49
+
50
+ is_span = false
51
+ is_shift = false
52
+ if next_span == next_shift
53
+ is_span = true
54
+ is_shift = true
55
+ elsif next_span < next_shift
56
+ is_span = true
57
+ else
58
+ is_shift = true
59
+ end
60
+
61
+ new_total_time = is_span ? next_span : next_shift
62
+ ts = new_total_time - total_time
63
+ total_time = new_total_time
64
+
65
+ if is_span
66
+ next_span += time_shift
67
+ end
68
+ if is_shift
69
+ next_shift += time_shift
70
+ end
71
+
72
+ m.subscription = scheduler.schedule_relative(ts, lambda {
73
+ gate.synchronize do
74
+ if is_shift
75
+ s = Subject.new
76
+ q.push s
77
+ observer.on_next(s.add_ref(ref_count_subscription))
78
+ end
79
+ if is_span
80
+ s = q.shift
81
+ s.on_completed
82
+ end
83
+ create_timer.call
84
+ end
85
+ })
86
+ }
87
+
88
+ q.push(Subject.new)
89
+ observer.on_next(q[0].add_ref(ref_count_subscription))
90
+ create_timer.call
91
+
92
+ new_obs = Observer.configure do |o|
93
+ o.on_next do |x|
94
+ gate.synchronize do
95
+ q.each {|s| s.on_next x}
96
+ end
97
+ end
98
+
99
+ o.on_error do |err|
100
+ gate.synchronize do
101
+ q.each {|s| s.on_error err}
102
+ observer.on_error err
103
+ end
104
+ end
105
+
106
+ o.on_completed do
107
+ gate.synchronize do
108
+ q.each {|s| s.on_on_completed}
109
+ observer.on_completed
110
+ end
111
+ end
112
+ end
113
+
114
+ group_subscription.push subscribe(new_obs)
115
+
116
+ ref_count_subscription
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,161 @@
1
+ # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
2
+
3
+ require 'thread'
4
+ require 'rx_ruby/core/observer'
5
+ require 'rx_ruby/core/observable'
6
+ require 'rx_ruby/subscriptions/subscription'
7
+
8
+ module RxRuby
9
+
10
+ # Represents the result of an asynchronous operation.
11
+ # Each notification is broadcasted to all subscribed observers.
12
+ class AsyncSubject
13
+
14
+ include Observable
15
+ include Observer
16
+
17
+ attr_reader :gate, :observers, :unsubscribed
18
+
19
+ def initialize
20
+ @observers = []
21
+ @gate = Mutex.new
22
+ @unsubscribed = false
23
+ @stopped = false
24
+ @error = nil
25
+ @value = nil
26
+ @has_value = false
27
+ end
28
+
29
+ # Indicates whether the subject has observers subscribed to it.
30
+ def has_observers?
31
+ observers && observers.length > 0
32
+ end
33
+
34
+ # Notifies all subscribed observers about the end of the sequence.
35
+ def on_completed
36
+ os = nil
37
+ v = nil
38
+ hv = false
39
+
40
+ gate.synchronize do
41
+ check_unsubscribed
42
+
43
+ unless @stopped
44
+ os = @observers.clone
45
+ @observers = []
46
+ @stopped = true
47
+ v = @value
48
+ hv = @has_value
49
+ end
50
+ end
51
+
52
+ if os
53
+ if hv
54
+ os.each do |o|
55
+ o.on_next @value
56
+ o.on_completed
57
+ end
58
+ else
59
+ os.each {|o| o.on_completed }
60
+ end
61
+ end
62
+ end
63
+
64
+ # Notifies all subscribed observers with the error.
65
+ def on_error(error)
66
+ raise 'error cannot be nil' unless error
67
+
68
+ os = nil
69
+ gate.synchronize do
70
+ check_unsubscribed
71
+
72
+ unless @stopped
73
+ os = observers.clone
74
+ @observers = []
75
+ @stopped = true
76
+ @error = error
77
+ end
78
+ end
79
+
80
+ os.each {|o| o.on_error error } if os
81
+ end
82
+
83
+ # Notifies all subscribed observers with the value.
84
+ def on_next(value)
85
+ gate.synchronize do
86
+ check_unsubscribed
87
+ unless @stopped
88
+ @value = value
89
+ @has_value = true
90
+ end
91
+ end
92
+ end
93
+
94
+ # Subscribes an observer to the subject.
95
+ def subscribe(observer)
96
+ raise 'observer cannot be nil' unless observer
97
+
98
+ err = nil
99
+ v = nil
100
+ hv = false
101
+
102
+ gate.synchronize do
103
+ check_unsubscribed
104
+
105
+ if !@stopped
106
+ observers.push(observer)
107
+ return InnerSubscription.new(self, observer)
108
+ end
109
+
110
+ err = @error
111
+ v = @value
112
+ hv = @has_value
113
+ end
114
+
115
+ if err
116
+ observer.on_next err
117
+ elsif hv
118
+ observer.on_next v
119
+ observer.on_completed
120
+ else
121
+ observer.on_completed
122
+ end
123
+
124
+ Subscription.empty
125
+ end
126
+
127
+ # Unsubscribe all observers and release resources.
128
+ def unsubscribe
129
+ gate.synchronize do
130
+ @unsubscribed = true
131
+ @observers = nil
132
+ @error = nil
133
+ @value = nil
134
+ end
135
+ end
136
+
137
+ class InnerSubscription
138
+ def initialize(subject, observer)
139
+ @subject = subject
140
+ @observer = observer
141
+ end
142
+
143
+ def unsubscribe
144
+ if @observer
145
+ @subject.gate.synchronize do
146
+ if !@subject.unsubscribed && @observer
147
+ @subject.observers.delete @observer
148
+ @observer = nil
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+
155
+ private
156
+
157
+ def check_unsubscribed
158
+ raise ArgumentError.new 'Subject unsubscribed' if unsubscribed
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,149 @@
1
+ # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
2
+
3
+ require 'thread'
4
+ require 'rx_ruby/core/observer'
5
+ require 'rx_ruby/core/observable'
6
+
7
+ module RxRuby
8
+
9
+ # Represents a value that changes over time.
10
+ # Observers can subscribe to the subject to receive the last (or initial) value and all subsequent notifications.
11
+ class BehaviorSubject
12
+
13
+ include Observable
14
+ include Observer
15
+
16
+ attr_reader :gate, :observers, :unsubscribed
17
+
18
+ def initialize(value)
19
+ @value = value
20
+ @observers = []
21
+ @gate = Mutex.new
22
+ @unsubscribed = false
23
+ @stopped = false
24
+ @error = nil
25
+ end
26
+
27
+ # Indicates whether the subject has observers subscribed to it.
28
+ def has_observers?
29
+ observers && observers.length > 0
30
+ end
31
+
32
+ # Gets the current value or throws an exception.
33
+ def value
34
+ gate.synchronize do
35
+ self.check_unsubscribed
36
+ raise @error if @error
37
+ @value
38
+ end
39
+ end
40
+
41
+ # Notifies all subscribed observers about the end of the sequence.
42
+ def on_completed
43
+ os = nil
44
+ @gate.synchronize do
45
+ self.check_unsubscribed
46
+
47
+ unless @stopped
48
+ os = @observers.clone
49
+ @observers = []
50
+ @stopped = true
51
+ end
52
+ end
53
+
54
+ os.each {|o| observer.on_completed } if os
55
+ end
56
+
57
+ # Notifies all subscribed observers with the error.
58
+ def on_error(error)
59
+ raise 'error cannot be nil' unless error
60
+
61
+ os = nil
62
+ @gate.synchronize do
63
+ self.check_unsubscribed
64
+
65
+ unless @stopped
66
+ os = @observers.clone
67
+ @observers = []
68
+ @stopped = true
69
+ @error = error
70
+ end
71
+ end
72
+
73
+ os.each {|o| observer.on_error error } if os
74
+ end
75
+
76
+ # Notifies all subscribed observers with the value.
77
+ def on_next(value)
78
+ os = nil
79
+ @gate.synchronize do
80
+ self.check_unsubscribed
81
+ @value = value
82
+ os = @observers.clone unless @stopped
83
+ end
84
+
85
+ os.each {|o| o.on_next value } if os
86
+ end
87
+
88
+ # Subscribes an observer to the subject.
89
+ def subscribe(observer)
90
+ raise 'observer cannot be nil' unless observer
91
+
92
+ err = nil
93
+ gate.synchronize do
94
+ self.check_unsubscribed
95
+
96
+ unless @stopped
97
+ observers.push(observer)
98
+ observer.on_next(@value)
99
+ return InnerSubscription.new(self, observer)
100
+ end
101
+
102
+ err = @error
103
+ end
104
+
105
+ if err
106
+ observer.on_next err
107
+ else
108
+ observer.on_completed
109
+ end
110
+
111
+ Subscription.empty
112
+ end
113
+
114
+ # Unsubscribe all observers and release resources.
115
+ def unsubscribe
116
+ gate.synchronize do
117
+ @unsubscribed = true
118
+ @observers = nil
119
+ @error = nil
120
+ @value = nil
121
+ end
122
+ end
123
+
124
+ class InnerSubscription
125
+ def initialize(subject, observer)
126
+ @subject = subject
127
+ @observer = observer
128
+ end
129
+
130
+ def unsubscribe
131
+ if @observer
132
+ @subject.gate.synchronize do
133
+ if !@subject.unsubscribed && @observer
134
+ @subject.observers.delete @observer
135
+ @observer = nil
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
141
+
142
+ private
143
+
144
+ def check_unsubscribed
145
+ raise 'Subject unsubscribed' if unsubscribed
146
+ end
147
+
148
+ end
149
+ end