rx_ruby 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitattributes +22 -0
- data/.gitignore +173 -0
- data/.travis.yml +10 -0
- data/Gemfile +4 -0
- data/Rakefile +11 -0
- data/examples/aggregate.rb +39 -0
- data/examples/amb.rb +25 -0
- data/examples/ambproto.rb +24 -0
- data/examples/and.rb +26 -0
- data/examples/as_observable.rb +25 -0
- data/examples/average.rb +43 -0
- data/examples/buffer_with_count.rb +44 -0
- data/examples/buffer_with_time.rb +51 -0
- data/examples/case.rb +29 -0
- data/examples/catch.rb +20 -0
- data/examples/catchproto.rb +39 -0
- data/examples/combine_latest.rb +35 -0
- data/examples/combine_latestproto.rb +33 -0
- data/examples/concat.rb +22 -0
- data/examples/concat_all.rb +27 -0
- data/examples/concat_map.rb +61 -0
- data/examples/concat_map_observer.rb +29 -0
- data/examples/concatproto.rb +25 -0
- data/examples/connect.rb +41 -0
- data/examples/contains.rb +37 -0
- data/examples/count.rb +36 -0
- data/examples/create.rb +55 -0
- data/examples/debounce.rb +35 -0
- data/examples/default_if_empty.rb +35 -0
- data/examples/defer.rb +20 -0
- data/examples/delay.rb +49 -0
- data/examples/delay_with_selector.rb +63 -0
- data/examples/dematerialize.rb +22 -0
- data/examples/disposable.rb +12 -0
- data/examples/distinct.rb +43 -0
- data/examples/distinct_until_changed.rb +43 -0
- data/examples/do.rb +59 -0
- data/examples/empty.rb +16 -0
- data/examples/for.rb +26 -0
- data/examples/fork_join.rb +23 -0
- data/examples/from.rb +106 -0
- data/examples/from_array.rb +21 -0
- data/examples/from_callback.rb +21 -0
- data/examples/generate.rb +24 -0
- data/examples/group_join.rb +39 -0
- data/examples/if.rb +46 -0
- data/examples/intervals.rb +26 -0
- data/examples/merge.rb +36 -0
- data/examples/merge_all.rb +27 -0
- data/examples/multicast.rb +32 -0
- data/examples/never.rb +15 -0
- data/examples/of.rb +19 -0
- data/examples/on_error_resume_next.rb +21 -0
- data/examples/pairs.rb +26 -0
- data/examples/publish.rb +79 -0
- data/examples/range.rb +19 -0
- data/examples/reduce.rb +18 -0
- data/examples/repeat.rb +19 -0
- data/examples/return.rb +17 -0
- data/examples/scan.rb +41 -0
- data/examples/start.rb +29 -0
- data/examples/throw.rb +17 -0
- data/examples/time_intervals.rb +28 -0
- data/examples/timer.rb +26 -0
- data/examples/timestamp.rb +28 -0
- data/examples/to_a.rb +23 -0
- data/examples/to_async.rb +26 -0
- data/examples/using.rb +52 -0
- data/examples/when.rb +26 -0
- data/examples/while.rb +25 -0
- data/examples/window_with_time.rb +78 -0
- data/examples/zip.rb +27 -0
- data/examples/zip_array.rb +25 -0
- data/lib/core_ext/enumerable.rb +22 -0
- data/lib/rx_ruby.rb +27 -0
- data/lib/rx_ruby/concurrency/async_lock.rb +57 -0
- data/lib/rx_ruby/concurrency/current_thread_scheduler.rb +75 -0
- data/lib/rx_ruby/concurrency/default_scheduler.rb +51 -0
- data/lib/rx_ruby/concurrency/historical_scheduler.rb +16 -0
- data/lib/rx_ruby/concurrency/immediate_scheduler.rb +68 -0
- data/lib/rx_ruby/concurrency/local_scheduler.rb +39 -0
- data/lib/rx_ruby/concurrency/periodic_scheduler.rb +74 -0
- data/lib/rx_ruby/concurrency/scheduled_item.rb +42 -0
- data/lib/rx_ruby/concurrency/scheduler.rb +150 -0
- data/lib/rx_ruby/concurrency/virtual_time_scheduler.rb +170 -0
- data/lib/rx_ruby/core/async_lock_observer.rb +46 -0
- data/lib/rx_ruby/core/auto_detach_observer.rb +59 -0
- data/lib/rx_ruby/core/checked_observer.rb +66 -0
- data/lib/rx_ruby/core/notification.rb +161 -0
- data/lib/rx_ruby/core/observable.rb +104 -0
- data/lib/rx_ruby/core/observe_on_observer.rb +50 -0
- data/lib/rx_ruby/core/observer.rb +119 -0
- data/lib/rx_ruby/core/scheduled_observer.rb +83 -0
- data/lib/rx_ruby/core/synchronized_observer.rb +47 -0
- data/lib/rx_ruby/core/time_interval.rb +17 -0
- data/lib/rx_ruby/internal/priority_queue.rb +122 -0
- data/lib/rx_ruby/internal/util.rb +9 -0
- data/lib/rx_ruby/joins/active_plan.rb +45 -0
- data/lib/rx_ruby/joins/join_observer.rb +51 -0
- data/lib/rx_ruby/joins/pattern.rb +14 -0
- data/lib/rx_ruby/joins/plan.rb +44 -0
- data/lib/rx_ruby/linq/connectable_observable.rb +34 -0
- data/lib/rx_ruby/linq/observable/_observable_timer_date_and_period.rb +22 -0
- data/lib/rx_ruby/linq/observable/_observable_timer_time_span.rb +14 -0
- data/lib/rx_ruby/linq/observable/_observable_timer_time_span_and_period.rb +20 -0
- data/lib/rx_ruby/linq/observable/aggregate.rb +7 -0
- data/lib/rx_ruby/linq/observable/and.rb +7 -0
- data/lib/rx_ruby/linq/observable/case.rb +15 -0
- data/lib/rx_ruby/linq/observable/concat_all.rb +7 -0
- data/lib/rx_ruby/linq/observable/concat_map.rb +35 -0
- data/lib/rx_ruby/linq/observable/concat_map_observer.rb +43 -0
- data/lib/rx_ruby/linq/observable/contains.rb +28 -0
- data/lib/rx_ruby/linq/observable/debounce.rb +41 -0
- data/lib/rx_ruby/linq/observable/delay.rb +81 -0
- data/lib/rx_ruby/linq/observable/delay_with_selector.rb +64 -0
- data/lib/rx_ruby/linq/observable/do.rb +42 -0
- data/lib/rx_ruby/linq/observable/for.rb +13 -0
- data/lib/rx_ruby/linq/observable/fork_join.rb +55 -0
- data/lib/rx_ruby/linq/observable/from.rb +34 -0
- data/lib/rx_ruby/linq/observable/group_join.rb +108 -0
- data/lib/rx_ruby/linq/observable/if.rb +17 -0
- data/lib/rx_ruby/linq/observable/interval.rb +5 -0
- data/lib/rx_ruby/linq/observable/multicast.rb +14 -0
- data/lib/rx_ruby/linq/observable/of.rb +11 -0
- data/lib/rx_ruby/linq/observable/pairs.rb +7 -0
- data/lib/rx_ruby/linq/observable/pluck.rb +7 -0
- data/lib/rx_ruby/linq/observable/publish.rb +11 -0
- data/lib/rx_ruby/linq/observable/start.rb +7 -0
- data/lib/rx_ruby/linq/observable/time_interval.rb +15 -0
- data/lib/rx_ruby/linq/observable/timer.rb +26 -0
- data/lib/rx_ruby/linq/observable/timestamp.rb +9 -0
- data/lib/rx_ruby/linq/observable/to_async.rb +40 -0
- data/lib/rx_ruby/linq/observable/when.rb +36 -0
- data/lib/rx_ruby/linq/observable/while.rb +41 -0
- data/lib/rx_ruby/operators/aggregates.rb +611 -0
- data/lib/rx_ruby/operators/creation.rb +220 -0
- data/lib/rx_ruby/operators/multiple.rb +735 -0
- data/lib/rx_ruby/operators/single.rb +399 -0
- data/lib/rx_ruby/operators/standard_query_operators.rb +279 -0
- data/lib/rx_ruby/operators/synchronization.rb +47 -0
- data/lib/rx_ruby/operators/time.rb +120 -0
- data/lib/rx_ruby/subjects/async_subject.rb +161 -0
- data/lib/rx_ruby/subjects/behavior_subject.rb +149 -0
- data/lib/rx_ruby/subjects/replay_subject.rb +39 -0
- data/lib/rx_ruby/subjects/subject.rb +131 -0
- data/lib/rx_ruby/subjects/subject_extensions.rb +45 -0
- data/lib/rx_ruby/subscriptions/composite_subscription.rb +91 -0
- data/lib/rx_ruby/subscriptions/ref_count_subscription.rb +88 -0
- data/lib/rx_ruby/subscriptions/scheduled_subscription.rb +32 -0
- data/lib/rx_ruby/subscriptions/serial_subscription.rb +60 -0
- data/lib/rx_ruby/subscriptions/single_assignment_subscription.rb +64 -0
- data/lib/rx_ruby/subscriptions/subscription.rb +56 -0
- data/lib/rx_ruby/testing/cold_observable.rb +45 -0
- data/lib/rx_ruby/testing/hot_observable.rb +47 -0
- data/lib/rx_ruby/testing/mock_observer.rb +33 -0
- data/lib/rx_ruby/testing/reactive_test.rb +94 -0
- data/lib/rx_ruby/testing/recorded.rb +17 -0
- data/lib/rx_ruby/testing/test_scheduler.rb +96 -0
- data/lib/rx_ruby/testing/test_subscription.rb +22 -0
- data/lib/rx_ruby/version.rb +3 -0
- data/license.txt +13 -0
- data/readme.md +152 -0
- data/rx_ruby.gemspec +22 -0
- data/test/rx_ruby/concurrency/helpers/historical_virtual_scheduler_helper.rb +135 -0
- data/test/rx_ruby/concurrency/helpers/immediate_local_scheduler_helper.rb +51 -0
- data/test/rx_ruby/concurrency/test_async_lock.rb +56 -0
- data/test/rx_ruby/concurrency/test_current_thread_scheduler.rb +44 -0
- data/test/rx_ruby/concurrency/test_default_scheduler.rb +44 -0
- data/test/rx_ruby/concurrency/test_historical_scheduler.rb +18 -0
- data/test/rx_ruby/concurrency/test_immediate_scheduler.rb +53 -0
- data/test/rx_ruby/concurrency/test_local_scheduler.rb +12 -0
- data/test/rx_ruby/concurrency/test_periodic_scheduler.rb +53 -0
- data/test/rx_ruby/concurrency/test_scheduled_item.rb +50 -0
- data/test/rx_ruby/concurrency/test_scheduler.rb +128 -0
- data/test/rx_ruby/concurrency/test_virtual_time_scheduler.rb +14 -0
- data/test/rx_ruby/core/test_notification.rb +129 -0
- data/test/rx_ruby/core/test_observable_creation.rb +483 -0
- data/test/rx_ruby/core/test_observer.rb +634 -0
- data/test/rx_ruby/internal/test_priority_queue.rb +71 -0
- data/test/rx_ruby/subscriptions/test_composite_subscription.rb +116 -0
- data/test/rx_ruby/subscriptions/test_serial_subscription.rb +62 -0
- data/test/rx_ruby/subscriptions/test_singleassignment_subscription.rb +61 -0
- data/test/rx_ruby/subscriptions/test_subscription.rb +27 -0
- data/test/test_helper.rb +11 -0
- metadata +291 -0
@@ -0,0 +1,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
|