├── .gitattributes ├── .gitignore ├── .travis.yml ├── Gemfile ├── Rakefile ├── examples ├── aggregate.rb ├── amb.rb ├── ambproto.rb ├── and.rb ├── as_observable.rb ├── average.rb ├── buffer_with_count.rb ├── buffer_with_time.rb ├── case.rb ├── catch.rb ├── catchproto.rb ├── combine_latest.rb ├── combine_latestproto.rb ├── concat.rb ├── concat_all.rb ├── concat_map.rb ├── concat_map_observer.rb ├── concatproto.rb ├── connect.rb ├── contains.rb ├── count.rb ├── create.rb ├── debounce.rb ├── default_if_empty.rb ├── defer.rb ├── delay.rb ├── delay_with_selector.rb ├── dematerialize.rb ├── disposable.rb ├── distinct.rb ├── distinct_until_changed.rb ├── do.rb ├── empty.rb ├── for.rb ├── fork_join.rb ├── from.rb ├── from_array.rb ├── from_callback.rb ├── generate.rb ├── group_join.rb ├── if.rb ├── intervals.rb ├── merge.rb ├── merge_all.rb ├── multicast.rb ├── never.rb ├── of.rb ├── on_error_resume_next.rb ├── pairs.rb ├── publish.rb ├── range.rb ├── reduce.rb ├── repeat.rb ├── return.rb ├── sample.rb ├── scan.rb ├── start.rb ├── throw.rb ├── time_intervals.rb ├── timer.rb ├── timestamp.rb ├── to_a.rb ├── to_async.rb ├── using.rb ├── when.rb ├── while.rb ├── window_with_time.rb ├── zip.rb └── zip_array.rb ├── lib ├── core_ext │ └── enumerable.rb ├── rx.rb └── rx │ ├── concurrency │ ├── async_lock.rb │ ├── current_thread_scheduler.rb │ ├── default_scheduler.rb │ ├── historical_scheduler.rb │ ├── immediate_scheduler.rb │ ├── local_scheduler.rb │ ├── periodic_scheduler.rb │ ├── scheduled_item.rb │ ├── scheduler.rb │ └── virtual_time_scheduler.rb │ ├── core │ ├── async_lock_observer.rb │ ├── auto_detach_observer.rb │ ├── checked_observer.rb │ ├── notification.rb │ ├── observable.rb │ ├── observe_on_observer.rb │ ├── observer.rb │ ├── scheduled_observer.rb │ ├── synchronized_observer.rb │ └── time_interval.rb │ ├── internal │ ├── priority_queue.rb │ └── util.rb │ ├── joins │ ├── active_plan.rb │ ├── join_observer.rb │ ├── pattern.rb │ └── plan.rb │ ├── linq │ ├── connectable_observable.rb │ └── observable │ │ ├── _observable_timer_date_and_period.rb │ │ ├── _observable_timer_time_span.rb │ │ ├── _observable_timer_time_span_and_period.rb │ │ ├── aggregate.rb │ │ ├── and.rb │ │ ├── case.rb │ │ ├── concat_all.rb │ │ ├── concat_map.rb │ │ ├── concat_map_observer.rb │ │ ├── contains.rb │ │ ├── debounce.rb │ │ ├── delay.rb │ │ ├── delay_with_selector.rb │ │ ├── do.rb │ │ ├── for.rb │ │ ├── fork_join.rb │ │ ├── from.rb │ │ ├── group_join.rb │ │ ├── if.rb │ │ ├── interval.rb │ │ ├── multicast.rb │ │ ├── of.rb │ │ ├── pairs.rb │ │ ├── pluck.rb │ │ ├── publish.rb │ │ ├── sample.rb │ │ ├── start.rb │ │ ├── time_interval.rb │ │ ├── timer.rb │ │ ├── timestamp.rb │ │ ├── to_async.rb │ │ ├── when.rb │ │ └── while.rb │ ├── operators │ ├── aggregates.rb │ ├── creation.rb │ ├── multiple.rb │ ├── single.rb │ ├── standard_query_operators.rb │ ├── synchronization.rb │ └── time.rb │ ├── subjects │ ├── async_subject.rb │ ├── behavior_subject.rb │ ├── replay_subject.rb │ ├── subject.rb │ └── subject_extensions.rb │ ├── subscriptions │ ├── composite_subscription.rb │ ├── ref_count_subscription.rb │ ├── scheduled_subscription.rb │ ├── serial_subscription.rb │ ├── single_assignment_subscription.rb │ └── subscription.rb │ ├── testing │ ├── cold_observable.rb │ ├── hot_observable.rb │ ├── mock_observer.rb │ ├── reactive_test.rb │ ├── recorded.rb │ ├── test_scheduler.rb │ └── test_subscription.rb │ └── version.rb ├── license.txt ├── readme.md ├── rx.gemspec └── test ├── rx ├── concurrency │ ├── helpers │ │ ├── historical_virtual_scheduler_helper.rb │ │ └── immediate_local_scheduler_helper.rb │ ├── test_async_lock.rb │ ├── test_current_thread_scheduler.rb │ ├── test_default_scheduler.rb │ ├── test_historical_scheduler.rb │ ├── test_immediate_scheduler.rb │ ├── test_local_scheduler.rb │ ├── test_periodic_scheduler.rb │ ├── test_scheduled_item.rb │ ├── test_scheduler.rb │ └── test_virtual_time_scheduler.rb ├── core │ ├── test_notification.rb │ ├── test_observable_creation.rb │ └── test_observer.rb ├── internal │ └── test_priority_queue.rb ├── linq │ └── observable │ │ └── test_sample.rb └── subscriptions │ ├── test_composite_subscription.rb │ ├── test_serial_subscription.rb │ ├── test_singleassignment_subscription.rb │ └── test_subscription.rb └── test_helper.rb /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Ruby 3 | ################# 4 | .ruby-version 5 | pkg/ 6 | Gemfile.lock 7 | 8 | ################# 9 | ## Eclipse 10 | ################# 11 | 12 | *.pydevproject 13 | .project 14 | .metadata 15 | bin/ 16 | tmp/ 17 | *.tmp 18 | *.bak 19 | *.swp 20 | *~.nib 21 | local.properties 22 | .classpath 23 | .settings/ 24 | .loadpath 25 | 26 | # External tool builders 27 | .externalToolBuilders/ 28 | 29 | # Locally stored "Eclipse launch configurations" 30 | *.launch 31 | 32 | # CDT-specific 33 | .cproject 34 | 35 | # PDT-specific 36 | .buildpath 37 | 38 | 39 | ################# 40 | ## Visual Studio 41 | ################# 42 | 43 | ## Ignore Visual Studio temporary files, build results, and 44 | ## files generated by popular Visual Studio add-ons. 45 | 46 | # User-specific files 47 | *.suo 48 | *.user 49 | *.sln.docstates 50 | 51 | # Build results 52 | [Dd]ebug/ 53 | [Rr]elease/ 54 | *_i.c 55 | *_p.c 56 | *.ilk 57 | *.meta 58 | *.obj 59 | *.pch 60 | *.pdb 61 | *.pgc 62 | *.pgd 63 | *.rsp 64 | *.sbr 65 | *.tlb 66 | *.tli 67 | *.tlh 68 | *.tmp 69 | *.vspscc 70 | .builds 71 | *.dotCover 72 | 73 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 74 | #packages/ 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opensdf 81 | *.sdf 82 | 83 | # Visual Studio profiler 84 | *.psess 85 | *.vsp 86 | 87 | # ReSharper is a .NET coding add-in 88 | _ReSharper* 89 | 90 | # Installshield output folder 91 | [Ee]xpress 92 | 93 | # DocProject is a documentation generator add-in 94 | DocProject/buildhelp/ 95 | DocProject/Help/*.HxT 96 | DocProject/Help/*.HxC 97 | DocProject/Help/*.hhc 98 | DocProject/Help/*.hhk 99 | DocProject/Help/*.hhp 100 | DocProject/Help/Html2 101 | DocProject/Help/html 102 | 103 | # Click-Once directory 104 | publish 105 | 106 | # Others 107 | [Bb]in 108 | [Oo]bj 109 | sql 110 | TestResults 111 | *.Cache 112 | ClientBin 113 | stylecop.* 114 | ~$* 115 | *.dbmdl 116 | Generated_Code #added for RIA/Silverlight projects 117 | 118 | # Backup & report files from converting an old project file to a newer 119 | # Visual Studio version. Backup files are not needed, because we have git ;-) 120 | _UpgradeReport_Files/ 121 | Backup*/ 122 | UpgradeLog*.XML 123 | 124 | 125 | 126 | ############ 127 | ## Windows 128 | ############ 129 | 130 | # Windows image file caches 131 | Thumbs.db 132 | 133 | # Folder config file 134 | Desktop.ini 135 | 136 | 137 | ############# 138 | ## Python 139 | ############# 140 | 141 | *.py[co] 142 | 143 | # Packages 144 | *.egg 145 | *.egg-info 146 | dist 147 | build 148 | eggs 149 | parts 150 | bin 151 | var 152 | sdist 153 | develop-eggs 154 | .installed.cfg 155 | 156 | # Installer logs 157 | pip-log.txt 158 | 159 | # Unit test / coverage reports 160 | .coverage 161 | .tox 162 | 163 | #Translations 164 | *.mo 165 | 166 | #Mr Developer 167 | .mr.developer.cfg 168 | 169 | # Mac crap 170 | .DS_Store 171 | 172 | # RubyMine 173 | .idea 174 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | sudo: false 3 | dist: trusty 4 | cache: bundler 5 | rvm: 6 | - 1.9.3 7 | - 2.0.0 8 | - 2.1.2 9 | - 2.2.0 10 | - 2.3.0 11 | - jruby-19mode # JRuby in 1.9 mode 12 | - rbx-2.6 13 | before_install: 14 | - gem update bundler 15 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | # Specify your gem's dependencies in rx.gemspec 4 | gemspec 5 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | require "bundler/gem_tasks" 3 | require 'rake/clean' 4 | require 'rake/testtask' 5 | 6 | task default: :test 7 | 8 | Rake::TestTask.new do |task| 9 | task.libs.unshift(File.expand_path('../test', __FILE__)) 10 | task.test_files = FileList['test/**/test*.rb'] 11 | end 12 | -------------------------------------------------------------------------------- /examples/aggregate.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using a seed for the accumulate 4 | source = Rx::Observable.range(1, 10).aggregate(1) {|acc, x| 5 | acc * x 6 | } 7 | 8 | subscription = source.subscribe( 9 | lambda {|x| 10 | puts 'Next: ' + x.to_s 11 | }, 12 | lambda {|err| 13 | puts 'Error: ' + err.to_s 14 | }, 15 | lambda { 16 | puts 'Completed' 17 | }) 18 | 19 | # => Next: 3628800 20 | # => Completed 21 | 22 | # Without a seed 23 | source = Rx::Observable.range(1, 10).aggregate {|acc, x| 24 | acc + x 25 | } 26 | 27 | subscription = source.subscribe( 28 | lambda {|x| 29 | puts 'Next: ' + x.to_s 30 | }, 31 | lambda {|err| 32 | puts 'Error: ' + err 33 | }, 34 | lambda { 35 | puts 'Completed' 36 | }) 37 | 38 | # => Next: 55 39 | # => Completed 40 | -------------------------------------------------------------------------------- /examples/amb.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using Observable sequences 4 | source = Rx::Observable.amb( 5 | Rx::Observable.timer(0.5).map { 'foo' }, 6 | Rx::Observable.timer(0.2).map { 'bar' } 7 | ) 8 | 9 | subscription = source.subscribe( 10 | lambda {|x| 11 | puts 'Next: ' + x.to_s 12 | }, 13 | lambda {|err| 14 | puts 'Error: ' + err.to_s 15 | }, 16 | lambda { 17 | puts 'Completed' 18 | }) 19 | 20 | # => Next: bar 21 | # => Completed 22 | 23 | while Thread.list.size > 1 24 | (Thread.list - [Thread.current]).each &:join 25 | end 26 | -------------------------------------------------------------------------------- /examples/ambproto.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | first = Rx::Observable.timer(0.3).map { 'first' } 4 | second = Rx::Observable.timer(0.5).map { 'second' } 5 | 6 | source = first.amb(second) 7 | 8 | subscription = source.subscribe( 9 | lambda {|x| 10 | puts 'Next: ' + x.to_s 11 | }, 12 | lambda {|err| 13 | puts 'Error: ' + err.to_s 14 | }, 15 | lambda { 16 | puts 'Completed' 17 | }) 18 | 19 | # => Next: first 20 | # => Completed 21 | 22 | while Thread.list.size > 1 23 | (Thread.list - [Thread.current]).each &:join 24 | end 25 | -------------------------------------------------------------------------------- /examples/and.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Choice of either plan, the first set of timers or second set 4 | source = Rx::Observable.when( 5 | Rx::Observable.timer(0.2).and(Rx::Observable.timer(0.3)).then_do(lambda {|x, y| return 'first' }), 6 | Rx::Observable.timer(0.4).and(Rx::Observable.timer(0.5)).then_do(lambda {|x, y| return 'second' }), 7 | ) 8 | 9 | subscription = source.subscribe( 10 | lambda {|x| 11 | puts 'Next: ' + x.to_s 12 | }, 13 | lambda {|err| 14 | puts 'Error: ' + err.to_s 15 | }, 16 | lambda { 17 | puts 'Completed' 18 | }) 19 | 20 | # => Next: first 21 | # => Next: second 22 | # => Completed 23 | 24 | while Thread.list.size > 1 25 | (Thread.list - [Thread.current]).each &:join 26 | end 27 | -------------------------------------------------------------------------------- /examples/as_observable.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Create subject 4 | subject = Rx::AsyncSubject.new 5 | 6 | # Send a value 7 | subject.on_next(42) 8 | subject.on_completed 9 | 10 | # Hide its type 11 | source = subject.as_observable 12 | 13 | subscription = source.subscribe( 14 | lambda {|x| 15 | puts 'Next: ' + x.to_s 16 | }, 17 | lambda {|err| 18 | puts 'Error: ' + err.to_s 19 | }, 20 | lambda { 21 | puts 'Completed' 22 | }) 23 | 24 | # => Next: 42 25 | # => Completed 26 | -------------------------------------------------------------------------------- /examples/average.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Without a selector 4 | source = Rx::Observable.range(0, 9).average 5 | 6 | subscription = source.subscribe( 7 | lambda {|x| 8 | puts 'Next: ' + x.to_s 9 | }, 10 | lambda {|err| 11 | puts 'Error: ' + err.to_s 12 | }, 13 | lambda { 14 | puts 'Completed' 15 | }) 16 | 17 | # => Next: 4 18 | # => Completed 19 | 20 | # With a selector 21 | arr = [ 22 | { value: 1 }, 23 | { value: 2 }, 24 | { value: 3 } 25 | ] 26 | 27 | source = Rx::Observable.from_array(arr).average {|x| 28 | x[:value] 29 | } 30 | 31 | subscription = source.subscribe( 32 | lambda {|x| 33 | puts 'Next: ' + x.to_s 34 | }, 35 | lambda {|err| 36 | puts 'Error: ' + err.to_s 37 | }, 38 | lambda { 39 | puts 'Completed' 40 | }) 41 | 42 | # => Next: 2 43 | # => Completed 44 | -------------------------------------------------------------------------------- /examples/buffer_with_count.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Without a skip 4 | source = Rx::Observable.range(1, 6) 5 | .buffer_with_count(2) 6 | 7 | subscription = source.subscribe( 8 | lambda {|x| 9 | puts 'Next: ' + x.to_s 10 | }, 11 | lambda {|err| 12 | puts 'Error: ' + err.to_s 13 | }, 14 | lambda { 15 | puts 'Completed' 16 | }) 17 | 18 | # => Next: [1, 2] 19 | # => Next: [3, 4] 20 | # => Next: [5, 6] 21 | # => Completed 22 | 23 | # Using a skip 24 | source = Rx::Observable.range(1, 6) 25 | .buffer_with_count(2, 1) 26 | 27 | subscription = source.subscribe( 28 | lambda {|x| 29 | puts 'Next: ' + x.to_s 30 | }, 31 | lambda {|err| 32 | puts 'Error: ' + err.to_s 33 | }, 34 | lambda { 35 | puts 'Completed' 36 | }) 37 | 38 | # => Next: [1, 2] 39 | # => Next: [2, 3] 40 | # => Next: [3, 4] 41 | # => Next: [4, 5] 42 | # => Next: [5, 6] 43 | # => Next: [6] 44 | # => Completed 45 | -------------------------------------------------------------------------------- /examples/buffer_with_time.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Without a skip 4 | source = Rx::Observable.interval(0.1) 5 | .buffer_with_time(0.5) 6 | .take(3) 7 | 8 | subscription = source.subscribe( 9 | lambda {|x| 10 | puts 'Next: ' + x.to_s 11 | }, 12 | lambda {|err| 13 | puts 'Error: ' + err.to_s 14 | }, 15 | lambda { 16 | puts 'Completed' 17 | }) 18 | 19 | # => Next: [0, 1, 2, 3] 20 | # => Next: [4, 5, 6, 7, 8] 21 | # => Next: [9, 10, 11, 12, 13] 22 | # => Completed 23 | 24 | while Thread.list.size > 1 25 | (Thread.list - [Thread.current]).each &:join 26 | end 27 | 28 | # Using a skip 29 | source = Rx::Observable.interval(0.1) 30 | .buffer_with_time(0.5, 0.1) 31 | .take(3) 32 | 33 | subscription = source.subscribe( 34 | lambda {|x| 35 | puts 'Next: ' + x.to_s 36 | }, 37 | lambda {|err| 38 | puts 'Error: ' + err.to_s 39 | }, 40 | lambda { 41 | puts 'Completed' 42 | }) 43 | 44 | # => Next: [0, 1, 2, 3, 4] 45 | # => Next: [1, 2, 3, 4, 5] 46 | # => Next: [2, 3, 4, 5, 6] 47 | # => Completed 48 | 49 | while Thread.list.size > 1 50 | (Thread.list - [Thread.current]).each &:join 51 | end 52 | -------------------------------------------------------------------------------- /examples/case.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | sources = { 4 | 'foo' => Rx::Observable.return(42), 5 | 'bar' => Rx::Observable.return(56) 6 | } 7 | 8 | defaultSource = Rx::Observable.empty() 9 | 10 | source = Rx::Observable.case( 11 | lambda { 12 | 'foo' 13 | }, 14 | sources, 15 | defaultSource) 16 | 17 | subscription = source.subscribe( 18 | lambda {|x| 19 | puts 'Next: ' + x.to_s 20 | }, 21 | lambda {|err| 22 | puts 'Error: ' + err.to_s 23 | }, 24 | lambda { 25 | puts 'Completed' 26 | }) 27 | 28 | #=> Next: 42 29 | #=> Completed 30 | -------------------------------------------------------------------------------- /examples/catch.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | obs1 = Rx::Observable.raise_error(Exception.new('error')) 4 | obs2 = Rx::Observable.return(42) 5 | 6 | source = Rx::Observable.rescue_error(obs1, obs2) 7 | 8 | subscription = source.subscribe( 9 | lambda {|x| 10 | puts 'Next: ' + x.to_s 11 | }, 12 | lambda {|err| 13 | puts 'Error: ' + err.to_s 14 | }, 15 | lambda { 16 | puts 'Completed' 17 | }) 18 | 19 | # => Next: 42 20 | # => Completed 21 | -------------------------------------------------------------------------------- /examples/catchproto.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using a second observable 4 | source = Rx::Observable.raise_error(Exception.new) 5 | .rescue_error(Rx::Observable.return(42)) 6 | 7 | subscription = source.subscribe( 8 | lambda {|x| 9 | puts 'Next: ' + x.to_s 10 | }, 11 | lambda {|err| 12 | puts 'Error: ' + err.to_s 13 | }, 14 | lambda { 15 | puts 'Completed' 16 | }) 17 | 18 | # => Next: 42 19 | # => Completed 20 | 21 | # Using a handler function 22 | source = Rx::Observable.raise_error(Exception.new) 23 | .rescue_error {|e| 24 | Rx::Observable.return(e.is_a? Exception) 25 | } 26 | 27 | subscription = source.subscribe( 28 | lambda {|x| 29 | puts 'Next: ' + x.to_s 30 | }, 31 | lambda {|err| 32 | puts 'Error: ' + err.to_s 33 | }, 34 | lambda { 35 | puts 'Completed' 36 | }) 37 | 38 | # => Next: true 39 | # => Completed 40 | -------------------------------------------------------------------------------- /examples/combine_latest.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Have staggering intervals 4 | source1 = Rx::Observable.interval(0.1) 5 | .map {|i| 'First: ' + i.to_s } 6 | 7 | source2 = Rx::Observable.interval(0.15) 8 | .map {|i| 'Second: ' + i.to_s } 9 | 10 | # Combine latest of source1 and source2 whenever either gives a value 11 | source = Rx::Observable.combine_latest( 12 | source1, 13 | source2) {|s1, s2| s1.to_s + ', ' + s2.to_s } 14 | .take(4) 15 | 16 | subscription = source.subscribe( 17 | lambda {|x| 18 | puts 'Next: ' + x.to_s 19 | }, 20 | lambda {|err| 21 | puts 'Error: ' + err.to_s 22 | }, 23 | lambda { 24 | puts 'Completed' 25 | }) 26 | 27 | # => Next: First: 0, Second: 0 28 | # => Next: First: 1, Second: 0 29 | # => Next: First: 1, Second: 1 30 | # => Next: First: 2, Second: 1 31 | # => Completed 32 | 33 | while Thread.list.size > 1 34 | (Thread.list - [Thread.current]).each &:join 35 | end 36 | -------------------------------------------------------------------------------- /examples/combine_latestproto.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Have staggering intervals 4 | source1 = Rx::Observable.interval(0.1) 5 | .map {|i| 'First: ' + i.to_s } 6 | 7 | source2 = Rx::Observable.interval(0.15) 8 | .map {|i| 'Second: ' + i.to_s } 9 | 10 | # Combine latest of source1 and source2 whenever either gives a value 11 | source = source1.combine_latest(source2) {|s1, s2| s1 + ', ' + s2 } 12 | .take(4) 13 | 14 | subscription = source.subscribe( 15 | lambda {|x| 16 | puts 'Next: ' + x.to_s 17 | }, 18 | lambda {|err| 19 | puts 'Error: ' + err.to_s 20 | }, 21 | lambda { 22 | puts 'Completed' 23 | }) 24 | 25 | # => Next: First: 0, Second: 0 26 | # => Next: First: 1, Second: 0 27 | # => Next: First: 1, Second: 1 28 | # => Next: First: 2, Second: 1 29 | # => Completed 30 | 31 | while Thread.list.size > 1 32 | (Thread.list - [Thread.current]).each &:join 33 | end 34 | -------------------------------------------------------------------------------- /examples/concat.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using Observable sequences 4 | source1 = Rx::Observable.return(42) 5 | source2 = Rx::Observable.return(56) 6 | 7 | source = Rx::Observable.concat(source1, source2) 8 | 9 | subscription = source.subscribe( 10 | lambda {|x| 11 | puts 'Next: ' + x.to_s 12 | }, 13 | lambda {|err| 14 | puts 'Error: ' + err.to_s 15 | }, 16 | lambda { 17 | puts 'Completed' 18 | }) 19 | 20 | # => Next: 42 21 | # => Next: 56 22 | # => Completed 23 | -------------------------------------------------------------------------------- /examples/concat_all.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.range(0, 3) 4 | .map {|x| Rx::Observable.range(x, 3) } 5 | .concat_all 6 | 7 | subscription = source.subscribe( 8 | lambda {|x| 9 | puts 'Next: ' + x.to_s 10 | }, 11 | lambda {|err| 12 | puts 'Error: ' + err.to_s 13 | }, 14 | lambda { 15 | puts 'Completed' 16 | }) 17 | 18 | # => Next: 0 19 | # => Next: 1 20 | # => Next: 2 21 | # => Next: 1 22 | # => Next: 2 23 | # => Next: 3 24 | # => Next: 2 25 | # => Next: 3 26 | # => Next: 4 27 | # => Completed 28 | -------------------------------------------------------------------------------- /examples/concat_map.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.range(0, 5) 4 | .concat_map(lambda {|x, i| 5 | return Rx::Observable 6 | .interval(0.1) 7 | .take(x).map { i } 8 | }) 9 | 10 | subscription = source.subscribe( 11 | lambda {|x| 12 | puts 'Next: ' + x.to_s 13 | }, 14 | lambda {|err| 15 | puts 'Error: ' + err.to_s 16 | }, 17 | lambda { 18 | puts 'Completed' 19 | }) 20 | 21 | # => Next: 1 22 | # => Next: 2 23 | # => Next: 2 24 | # => Next: 3 25 | # => Next: 3 26 | # => Next: 3 27 | # => Next: 4 28 | # => Next: 4 29 | # => Next: 4 30 | # => Next: 4 31 | # => Completed 32 | 33 | while Thread.list.size > 1 34 | (Thread.list - [Thread.current]).each &:join 35 | end 36 | 37 | # Using an array 38 | source = Rx::Observable.of(1,2,3) 39 | .concat_map( 40 | lambda {|x, i| return [x,i] }, 41 | lambda {|x, y, ix, iy| return x + y + ix + iy } 42 | ) 43 | 44 | subscription = source.subscribe( 45 | lambda {|x| 46 | puts 'Next: ' + x.to_s 47 | }, 48 | lambda {|err| 49 | puts 'Error: ' + err.to_s 50 | }, 51 | lambda { 52 | puts 'Completed' 53 | }) 54 | 55 | # => Next: 2 56 | # => Next: 2 57 | # => Next: 5 58 | # => Next: 5 59 | # => Next: 8 60 | # => Next: 8 61 | # => Completed 62 | -------------------------------------------------------------------------------- /examples/concat_map_observer.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.range(1, 3) 4 | .concat_map_observer( 5 | lambda {|x, i| 6 | return Rx::Observable.repeat(x, i) 7 | }, 8 | lambda {|err| 9 | return Rx::Observable.return(42) 10 | }, 11 | lambda { 12 | return Rx::Observable.empty 13 | }) 14 | 15 | subscription = source.subscribe( 16 | lambda {|x| 17 | puts 'Next: ' + x.to_s 18 | }, 19 | lambda {|err| 20 | puts 'Error: ' + err.to_s 21 | }, 22 | lambda { 23 | puts 'Completed' 24 | }) 25 | 26 | # => Next: 2 27 | # => Next: 3 28 | # => Next: 3 29 | # => Completed 30 | -------------------------------------------------------------------------------- /examples/concatproto.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable 4 | .return(42) 5 | .concat(Rx::Observable.return(56), Rx::Observable.return(72)) 6 | 7 | subscription = source.subscribe( 8 | lambda {|x| 9 | puts 'Next: ' + x.to_s 10 | }, 11 | lambda {|err| 12 | puts 'Error: ' + err.to_s 13 | }, 14 | lambda { 15 | puts 'Completed' 16 | }) 17 | 18 | # => Next: 42 19 | # => Next: 56 20 | # => Next: 72 21 | # => Completed 22 | 23 | while Thread.list.size > 1 24 | (Thread.list - [Thread.current]).each &:join 25 | end 26 | -------------------------------------------------------------------------------- /examples/connect.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | interval = Rx::Observable.interval(1) 4 | 5 | source = interval 6 | .take(2) 7 | .do {|x| puts 'Side effect' } 8 | 9 | def create_observer(tag) 10 | return Rx::Observer.create( 11 | lambda {|x| 12 | puts 'Next: ' + tag + x.to_s 13 | }, 14 | lambda {|err| 15 | puts 'Error: ' + err.to_s 16 | }, 17 | lambda { 18 | puts 'Completed' 19 | }) 20 | end 21 | 22 | published = source.publish 23 | 24 | published.subscribe(create_observer('SourceA')) 25 | published.subscribe(create_observer('SourceB')) 26 | 27 | # Connect the source 28 | connection = published.connect 29 | 30 | # => Side effect 31 | # => Next: SourceA0 32 | # => Next: SourceB0 33 | # => Side effect 34 | # => Next: SourceA1 35 | # => Next: SourceB1 36 | # => Completed 37 | # => Completed 38 | 39 | while Thread.list.size > 1 40 | (Thread.list - [Thread.current]).each &:join 41 | end 42 | -------------------------------------------------------------------------------- /examples/contains.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Without an index 4 | source = Rx::Observable.of(42) 5 | .contains(42) 6 | 7 | subscription = source.subscribe( 8 | lambda {|x| 9 | puts 'Next: ' + x.to_s 10 | }, 11 | lambda {|err| 12 | puts 'Error: ' + err.to_s 13 | }, 14 | lambda { 15 | puts 'Completed' 16 | }) 17 | 18 | # => Next: true 19 | # => Completed 20 | 21 | # With an index 22 | source = Rx::Observable.of(1,2,3) 23 | .contains(2, 1) 24 | 25 | subscription = source.subscribe( 26 | lambda {|x| 27 | puts 'Next: ' + x.to_s 28 | }, 29 | lambda {|err| 30 | puts 'Error: ' + err.to_s 31 | }, 32 | lambda { 33 | puts 'Completed' 34 | }) 35 | 36 | # => Next: true 37 | # => Completed 38 | -------------------------------------------------------------------------------- /examples/count.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Without a predicate 4 | source = Rx::Observable.range(0, 10).count 5 | 6 | subscription = source.subscribe( 7 | lambda {|x| 8 | puts 'Next: ' + x.to_s 9 | }, 10 | lambda {|err| 11 | puts 'Error: ' + err.to_s 12 | }, 13 | lambda { 14 | puts 'Completed' 15 | }) 16 | 17 | # => Next: 10 18 | # => Completed 19 | 20 | # With a predicate 21 | source = Rx::Observable.range(0, 10) 22 | .count {|x| x % 2 === 0 } 23 | 24 | subscription = source.subscribe( 25 | lambda {|x| 26 | puts 'Next: ' + x.to_s 27 | }, 28 | lambda {|err| 29 | puts 'Error: ' + err.to_s 30 | }, 31 | lambda { 32 | puts 'Completed' 33 | }) 34 | 35 | # => Next: 5 36 | # => Completed 37 | -------------------------------------------------------------------------------- /examples/create.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using a function 4 | source = Rx::Observable.create {|observer| 5 | observer.on_next(42) 6 | observer.on_completed 7 | 8 | # Note that this is optional, you do not have to return this if you require no cleanup 9 | lambda { 10 | puts 'disposed' 11 | } 12 | } 13 | 14 | subscription = source.subscribe( 15 | lambda {|x| 16 | puts 'Next: ' + x.to_s 17 | }, 18 | lambda {|err| 19 | puts 'Error: ' + err.to_s 20 | }, 21 | lambda { 22 | puts 'Completed' 23 | }) 24 | 25 | # => Next: 42 26 | # => Completed 27 | 28 | subscription.dispose 29 | 30 | # => disposed 31 | 32 | # Using a disposable 33 | source = Rx::Observable.create {|observer| 34 | observer.on_next(42) 35 | observer.on_completed 36 | 37 | # Note that this is optional, you do not have to return this if you require no cleanup 38 | Rx::Disposable.create { 39 | puts 'disposed' 40 | } 41 | } 42 | 43 | subscription = source.subscribe( 44 | lambda {|x| 45 | puts 'Next: ' + x.to_s 46 | }, 47 | lambda {|err| 48 | puts 'Error: ' + err.to_s 49 | }, 50 | lambda { 51 | puts 'Completed' 52 | }) 53 | 54 | # => Next: 42 55 | # => Completed 56 | -------------------------------------------------------------------------------- /examples/debounce.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | times = [ 4 | { value: 0, time: 0.1 }, 5 | { value: 1, time: 0.6 }, 6 | { value: 2, time: 0.4 }, 7 | { value: 3, time: 0.7 }, 8 | { value: 4, time: 0.2 } 9 | ] 10 | 11 | # Delay each item by time and project value 12 | source = Rx::Observable.from(times) 13 | .flat_map {|item| 14 | Rx::Observable.of(item[:value]) 15 | .delay(item[:time]) 16 | } 17 | .debounce 0.5 # ms 18 | 19 | subscription = source.subscribe( 20 | lambda {|x| 21 | puts 'Next: ' + x.to_s 22 | }, 23 | lambda {|err| 24 | puts 'Error: ' + err.to_s 25 | }, 26 | lambda { 27 | puts 'Completed' 28 | }) 29 | 30 | # => Next: 3 31 | # => Completed 32 | 33 | while Thread.list.size > 1 34 | (Thread.list - [Thread.current]).each &:join 35 | end 36 | -------------------------------------------------------------------------------- /examples/default_if_empty.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Without a default value 4 | source = Rx::Observable.empty.default_if_empty 5 | 6 | subscription = source.subscribe( 7 | lambda {|x| 8 | puts 'Next: ' + x.inspect 9 | }, 10 | lambda {|err| 11 | puts 'Error: ' + err.to_s 12 | }, 13 | lambda { 14 | puts 'Completed' 15 | }) 16 | 17 | # => Next: nil 18 | # => Completed 19 | 20 | # With a default_value 21 | source = Rx::Observable.empty.default_if_empty(false) 22 | 23 | subscription = source.subscribe( 24 | lambda {|x| 25 | puts 'Next: ' + x.to_s 26 | }, 27 | lambda {|err| 28 | puts 'Error: ' + err.to_s 29 | }, 30 | lambda { 31 | puts 'Completed' 32 | }) 33 | 34 | # => Next: false 35 | # => Completed 36 | -------------------------------------------------------------------------------- /examples/defer.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using an observable sequence 4 | source = Rx::Observable.defer { 5 | Rx::Observable.return(42) 6 | } 7 | 8 | subscription = source.subscribe( 9 | lambda {|x| 10 | puts 'Next: ' + x.to_s 11 | }, 12 | lambda {|err| 13 | puts 'Error: ' + err.to_s 14 | }, 15 | lambda { 16 | puts 'Completed' 17 | }) 18 | 19 | # => Next: 42 20 | # => Completed 21 | -------------------------------------------------------------------------------- /examples/delay.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using an absolute time to delay by a second 4 | source = Rx::Observable.range(0, 3) 5 | .delay(Time.now + 1) 6 | 7 | subscription = source.subscribe( 8 | lambda {|x| 9 | puts 'Next: ' + x.to_s 10 | }, 11 | lambda {|err| 12 | puts 'Error: ' + err.to_s 13 | }, 14 | lambda { 15 | puts 'Completed' 16 | }) 17 | 18 | # => Next: 0 19 | # => Next: 1 20 | # => Next: 2 21 | # => Completed 22 | 23 | while Thread.list.size > 1 24 | (Thread.list - [Thread.current]).each &:join 25 | end 26 | 27 | # Using an relatove time to delay by a second 28 | source = Rx::Observable.range(0, 3) 29 | .delay(1) 30 | 31 | subscription = source.subscribe( 32 | lambda {|x| 33 | puts 'Next: ' + x.to_s 34 | }, 35 | lambda {|err| 36 | puts 'Error: ' + err.to_s 37 | }, 38 | lambda { 39 | puts 'Completed' 40 | }) 41 | 42 | # => Next: 0 43 | # => Next: 1 44 | # => Next: 2 45 | # => Completed 46 | 47 | while Thread.list.size > 1 48 | (Thread.list - [Thread.current]).each &:join 49 | end 50 | -------------------------------------------------------------------------------- /examples/delay_with_selector.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # With subscription_delay 4 | source = Rx::Observable 5 | .range(0, 3) 6 | .delay_with_selector( 7 | Rx::Observable.timer(0.3), 8 | lambda {|x| 9 | return Rx::Observable.timer(x * 0.4) 10 | } 11 | ) 12 | .time_interval 13 | .map {|x| x.value.to_s + ':' + x.interval.to_s } 14 | 15 | subscription = source.subscribe( 16 | lambda {|x| 17 | puts 'Next: ' + x.to_s 18 | }, 19 | lambda {|err| 20 | puts 'Error: ' + err.to_s 21 | }, 22 | lambda { 23 | puts 'Completed' 24 | }) 25 | 26 | # => Next: 0:0.3 27 | # => Next: 1:0.4 28 | # => Next: 2:0.4 29 | # => Completed 30 | 31 | while Thread.list.size > 1 32 | (Thread.list - [Thread.current]).each &:join 33 | end 34 | 35 | # Without subscription_delay 36 | source = Rx::Observable 37 | .range(0, 3) 38 | .delay_with_selector( 39 | lambda {|x| 40 | return Rx::Observable.timer(x * 0.4) 41 | }) 42 | .time_interval 43 | .map {|x| x.value.to_s + ':' + x.interval.to_s } 44 | 45 | subscription = source.subscribe( 46 | lambda {|x| 47 | puts 'Next: ' + x.to_s 48 | }, 49 | lambda {|err| 50 | puts 'Error: ' + err.to_s 51 | }, 52 | lambda { 53 | puts 'Completed' 54 | }) 55 | 56 | # => Next: 0:0 57 | # => Next: 1:0.4 58 | # => Next: 2:0.4 59 | # => Completed 60 | 61 | while Thread.list.size > 1 62 | (Thread.list - [Thread.current]).each &:join 63 | end 64 | -------------------------------------------------------------------------------- /examples/dematerialize.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable 4 | .from_array([ 5 | Rx::Notification.create_on_next(42), 6 | Rx::Notification.create_on_error(Exception.new('woops')) 7 | ]) 8 | .dematerialize 9 | 10 | subscription = source.subscribe( 11 | lambda {|x| 12 | puts 'Next: ' + x.to_s 13 | }, 14 | lambda {|err| 15 | puts 'Error: ' + err.to_s 16 | }, 17 | lambda { 18 | puts 'Completed' 19 | }) 20 | 21 | # => Next: 42 22 | # => Error: woops 23 | -------------------------------------------------------------------------------- /examples/disposable.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | disposable = Rx::Disposable.create { 4 | puts 'disposed' 5 | } 6 | 7 | disposable.dispose 8 | # => disposed 9 | 10 | disposable = Rx::Disposable.empty 11 | 12 | disposable.dispose # Does nothing 13 | -------------------------------------------------------------------------------- /examples/distinct.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Without key selector 4 | source = Rx::Observable.from_array([ 5 | 42, 24, 42, 24 6 | ]) 7 | .distinct 8 | 9 | subscription = source.subscribe( 10 | lambda {|x| 11 | puts 'Next: ' + x.to_s 12 | }, 13 | lambda {|err| 14 | puts 'Error: ' + err.to_s 15 | }, 16 | lambda { 17 | puts 'Completed' 18 | }) 19 | 20 | # => Next: 42 21 | # => Next: 24 22 | # => Completed 23 | 24 | # With key selector 25 | source = Rx::Observable.from_array([ 26 | {value: 42}, {value: 24}, {value: 42}, {value: 24} 27 | ]) 28 | .distinct {|x| x[:value] } 29 | 30 | subscription = source.subscribe( 31 | lambda {|x| 32 | puts 'Next: ' + x.to_s 33 | }, 34 | lambda {|err| 35 | puts 'Error: ' + err.to_s 36 | }, 37 | lambda { 38 | puts 'Completed' 39 | }) 40 | 41 | # => Next: {:value=>42} 42 | # => Next: {:value=>24} 43 | # => Completed 44 | -------------------------------------------------------------------------------- /examples/distinct_until_changed.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Without key selector 4 | source = Rx::Observable.from_array([ 5 | 42, 42, 24, 24 6 | ]) 7 | .distinct_until_changed 8 | 9 | subscription = source.subscribe( 10 | lambda {|x| 11 | puts 'Next: ' + x.to_s 12 | }, 13 | lambda {|err| 14 | puts 'Error: ' + err.to_s 15 | }, 16 | lambda { 17 | puts 'Completed' 18 | }) 19 | 20 | # => Next: 42 21 | # => Next: 24 22 | # => Completed 23 | 24 | # With key selector 25 | source = Rx::Observable.from_array([ 26 | {value: 42}, {value: 42}, {value: 24}, {value: 24} 27 | ]) 28 | .distinct_until_changed {|x| x[:value] } 29 | 30 | subscription = source.subscribe( 31 | lambda {|x| 32 | puts 'Next: ' + x.to_s 33 | }, 34 | lambda {|err| 35 | puts 'Error: ' + err.to_s 36 | }, 37 | lambda { 38 | puts 'Completed' 39 | }) 40 | 41 | # => Next: {:value=>42} 42 | # => Next: {:value=>24} 43 | # => Completed 44 | -------------------------------------------------------------------------------- /examples/do.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using a function 4 | source = Rx::Observable.range(0, 3) 5 | .do( 6 | lambda {|x| puts 'Do Next:' + x.to_s }, 7 | lambda {|err| puts 'Do Error:' + err.to_s }, 8 | lambda { puts 'Do Completed' } 9 | ) 10 | 11 | subscription = source.subscribe( 12 | lambda {|x| 13 | puts 'Next: ' + x.to_s 14 | }, 15 | lambda {|err| 16 | puts 'Error: ' + err.to_s 17 | }, 18 | lambda { 19 | puts 'Completed' 20 | }) 21 | 22 | # => Do Next: 0 23 | # => Next: 0 24 | # => Do Next: 1 25 | # => Next: 1 26 | # => Do Next: 2 27 | # => Next: 2 28 | # => Do Completed 29 | # => Completed 30 | 31 | # Using an observer 32 | observer = Rx::Observer.create( 33 | lambda {|x| puts 'Do Next: ' + x.to_s }, 34 | lambda {|err| puts 'Do Error: ' + err.to_s }, 35 | lambda { puts 'Do Completed' } 36 | ) 37 | 38 | source = Rx::Observable.range(0, 3) 39 | .do(observer) 40 | 41 | subscription = source.subscribe( 42 | lambda {|x| 43 | puts 'Next: ' + x.to_s 44 | }, 45 | lambda {|err| 46 | puts 'Error: ' + err.to_s 47 | }, 48 | lambda { 49 | puts 'Completed' 50 | }) 51 | 52 | # => Do Next: 0 53 | # => Next: 0 54 | # => Do Next: 1 55 | # => Next: 1 56 | # => Do Next: 2 57 | # => Next: 2 58 | # => Do Completed 59 | # => Completed 60 | -------------------------------------------------------------------------------- /examples/empty.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.empty 4 | 5 | subscription = source.subscribe( 6 | lambda {|x| 7 | puts 'Next: ' + x.to_s 8 | }, 9 | lambda {|err| 10 | puts 'Error: ' + err.to_s 11 | }, 12 | lambda { 13 | puts 'Completed' 14 | }) 15 | 16 | # => Completed 17 | -------------------------------------------------------------------------------- /examples/for.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using Observables 4 | array = [1, 2, 3] 5 | 6 | source = Rx::Observable.for( 7 | array, 8 | lambda {|x| 9 | Rx::Observable.return(x) 10 | }) 11 | 12 | subscription = source.subscribe( 13 | lambda {|x| 14 | puts 'Next: ' + x.to_s 15 | }, 16 | lambda {|err| 17 | puts 'Error: ' + err.to_s 18 | }, 19 | lambda { 20 | puts 'Completed' 21 | }) 22 | 23 | # => Next: 1 24 | # => Next: 2 25 | # => Next: 3 26 | # => Completed 27 | -------------------------------------------------------------------------------- /examples/fork_join.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using observables and Promises 4 | source = Rx::Observable.fork_join( 5 | Rx::Observable.return(42), 6 | Rx::Observable.range(0, 10), 7 | Rx::Observable.from_array([1,2,3]), 8 | Rx::Observable.return(56) 9 | ) 10 | 11 | subscription = source.subscribe( 12 | lambda {|x| 13 | puts 'Next: ' + x.to_s 14 | }, 15 | lambda {|err| 16 | puts 'Error: ' + err.to_s 17 | }, 18 | lambda { 19 | puts 'Completed' 20 | }) 21 | 22 | # => Next: [42, 9, 3, 56] 23 | # => Completed 24 | -------------------------------------------------------------------------------- /examples/from.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Array-like object (arguments) to Observable 4 | def f(*arguments) 5 | Rx::Observable.from(arguments) 6 | end 7 | 8 | f(1, 2, 3).subscribe( 9 | lambda {|x| 10 | puts 'Next: ' + x.to_s 11 | }, 12 | lambda {|err| 13 | puts 'Error: ' + err.to_s 14 | }, 15 | lambda { 16 | puts 'Completed' 17 | }) 18 | 19 | # => Next: 1 20 | # => Next: 2 21 | # => Next: 3 22 | # => Completed 23 | 24 | # Any iterable object... 25 | s = ["foo", :window] 26 | Rx::Observable.from(s).subscribe( 27 | lambda {|x| 28 | puts 'Next: ' + x.to_s 29 | }, 30 | lambda {|err| 31 | puts 'Error: ' + err.to_s 32 | }, 33 | lambda { 34 | puts 'Completed' 35 | }) 36 | # => Next: foo 37 | # => Next: window 38 | # => Completed 39 | 40 | # Map 41 | m = {1 => 2, 2 => 4, 4 => 8} 42 | Rx::Observable.from(m).subscribe( 43 | lambda {|x| 44 | puts 'Next: ' + x.to_s 45 | }, 46 | lambda {|err| 47 | puts 'Error: ' + err.to_s 48 | }, 49 | lambda { 50 | puts 'Completed' 51 | }) 52 | # => Next: [1, 2] 53 | # => Next: [2, 4] 54 | # => Next: [4, 8] 55 | # => Completed 56 | 57 | # String 58 | Rx::Observable.from("foo".to_enum(:each_char)).subscribe( 59 | lambda {|x| 60 | puts 'Next: ' + x.to_s 61 | }, 62 | lambda {|err| 63 | puts 'Error: ' + err.to_s 64 | }, 65 | lambda { 66 | puts 'Completed' 67 | }) 68 | # => Next: f 69 | # => Next: o 70 | # => Next: o 71 | # => Completed 72 | 73 | # Using an arrow function as the map function to 74 | # manipulate the elements 75 | Rx::Observable.from([1, 2, 3], lambda {|x, i| x + x }).subscribe( 76 | lambda {|x| 77 | puts 'Next: ' + x.to_s 78 | }, 79 | lambda {|err| 80 | puts 'Error: ' + err.to_s 81 | }, 82 | lambda { 83 | puts 'Completed' 84 | }) 85 | # => Next: 2 86 | # => Next: 4 87 | # => Next: 6 88 | # => Completed 89 | 90 | # Generate a sequence of numbers 91 | Rx::Observable.from(5.times, lambda {|v, k| k }).subscribe( 92 | lambda {|x| 93 | puts 'Next: ' + x.to_s 94 | }, 95 | lambda {|err| 96 | puts 'Error: ' + err.to_s 97 | }, 98 | lambda { 99 | puts 'Completed' 100 | }) 101 | # => Next: 0 102 | # => Next: 1 103 | # => Next: 2 104 | # => Next: 3 105 | # => Next: 4 106 | # => Completed 107 | -------------------------------------------------------------------------------- /examples/from_array.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | array = [1,2,3] 4 | 5 | source = Rx::Observable.from_array(array) 6 | 7 | subscription = source.subscribe( 8 | lambda {|x| 9 | puts 'Next: ' + x.to_s 10 | }, 11 | lambda {|err| 12 | puts 'Error: ' + err.to_s 13 | }, 14 | lambda { 15 | puts 'Completed' 16 | }) 17 | 18 | # => Next: 1 19 | # => Next: 2 20 | # => Next: 3 21 | # => Completed 22 | -------------------------------------------------------------------------------- /examples/from_callback.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Wrap fs.exists 4 | exists = Rx::Observable.from_callback(File.method(:exist?)) 5 | 6 | # Check if file.txt exists 7 | source = exists.call('file.txt') 8 | 9 | subscription = source.subscribe( 10 | lambda {|x| 11 | puts 'Next: ' + x.to_s 12 | }, 13 | lambda {|err| 14 | puts 'Error: ' + err.to_s 15 | }, 16 | lambda { 17 | puts 'Completed' 18 | }) 19 | 20 | # => Next: true 21 | # => Completed 22 | -------------------------------------------------------------------------------- /examples/generate.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.generate( 4 | 0, 5 | lambda {|x| x < 3 }, # condition 6 | lambda {|x| x + 1 }, # iterate 7 | lambda {|x| x } # resultSelector 8 | ) 9 | 10 | subscription = source.subscribe( 11 | lambda {|x| 12 | puts 'Next: ' + x.to_s 13 | }, 14 | lambda {|err| 15 | puts 'Error: ' + err.to_s 16 | }, 17 | lambda { 18 | puts 'Completed' 19 | }) 20 | 21 | # => Next: 0 22 | # => Next: 1 23 | # => Next: 2 24 | # => Completed 25 | -------------------------------------------------------------------------------- /examples/group_join.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | xs = Rx::Observable.interval(0.1) 4 | .map {|x| 'first' + x.to_s } 5 | 6 | ys = Rx::Observable.interval(0.1) 7 | .map {|x| 'second' + x.to_s } 8 | 9 | source = xs.group_join( 10 | ys, 11 | lambda {|_| return Rx::Observable.timer(0) }, 12 | lambda {|_| return Rx::Observable.timer(0) }, 13 | lambda {|x, yy| 14 | return yy.map {|y| 15 | x + y 16 | } 17 | }).merge_all.take(5) 18 | 19 | subscription = source.subscribe( 20 | lambda {|x| 21 | puts 'Next: ' + x.to_s 22 | }, 23 | lambda {|err| 24 | puts 'Error: ' + err.to_s 25 | }, 26 | lambda { 27 | puts 'Completed' 28 | }) 29 | 30 | # => Next: first0second0 31 | # => Next: first1second1 32 | # => Next: first2second2 33 | # => Next: first3second3 34 | # => Next: first4second4 35 | # => Completed 36 | 37 | while Thread.list.size > 1 38 | (Thread.list - [Thread.current]).each &:join 39 | end 40 | -------------------------------------------------------------------------------- /examples/if.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # This uses and only then source 4 | should_run = true 5 | 6 | source = Rx::Observable.if( 7 | lambda { return should_run }, 8 | Rx::Observable.return(42) 9 | ) 10 | 11 | subscription = source.subscribe( 12 | lambda {|x| 13 | puts 'Next: ' + x.to_s 14 | }, 15 | lambda {|err| 16 | puts 'Error: ' + err.to_s 17 | }, 18 | lambda { 19 | puts 'Completed' 20 | }) 21 | 22 | # => Next: 42 23 | # => Completed 24 | 25 | # The next example uses an elseSource 26 | should_run = false 27 | 28 | source = Rx::Observable.if( 29 | lambda { return should_run }, 30 | Rx::Observable.return(42), 31 | Rx::Observable.return(56) 32 | ) 33 | 34 | subscription = source.subscribe( 35 | lambda {|x| 36 | puts 'Next: ' + x.to_s 37 | }, 38 | lambda {|err| 39 | puts 'Error: ' + err.to_s 40 | }, 41 | lambda { 42 | puts 'Completed' 43 | }) 44 | 45 | # => Next: 56 46 | # => Completed 47 | -------------------------------------------------------------------------------- /examples/intervals.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable 4 | .interval(0.5) # ms 5 | .time_interval 6 | .take(3) 7 | 8 | subscription = source.subscribe( 9 | lambda {|x| 10 | puts 'Next: ' + x.to_s 11 | }, 12 | lambda {|err| 13 | puts 'Error: ' + err.to_s 14 | }, 15 | lambda { 16 | puts 'Completed' 17 | }) 18 | 19 | # => Next: (0)@(0.5120988) 20 | # => Next: (1)@(0.5000763) 21 | # => Next: (2)@(0.515575) 22 | # => Completed 23 | 24 | while Thread.list.size > 1 25 | (Thread.list - [Thread.current]).each &:join 26 | end 27 | -------------------------------------------------------------------------------- /examples/merge.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source1 = Rx::Observable.interval(0.1) 4 | .time_interval() 5 | .pluck('interval') 6 | .take(3) 7 | source2 = Rx::Observable.interval(0.15) 8 | .time_interval() 9 | .pluck('interval') 10 | .take(2) 11 | 12 | source = Rx::Observable.merge( 13 | source1, 14 | source2) 15 | 16 | subscription = source.subscribe( 17 | lambda {|x| 18 | puts 'Next: ' + x.to_s 19 | }, 20 | lambda {|err| 21 | puts 'Error: ' + err.to_s 22 | }, 23 | lambda { 24 | puts 'Completed' 25 | }) 26 | 27 | # => Next: 100 28 | # => Next: 150 29 | # => Next: 100 30 | # => Next: 150 31 | # => Next: 100 32 | # => Completed 33 | 34 | while Thread.list.size > 1 35 | (Thread.list - [Thread.current]).each &:join 36 | end 37 | -------------------------------------------------------------------------------- /examples/merge_all.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.range(0, 3) 4 | .map {|x| Rx::Observable.range(x, 3) } 5 | .merge_all 6 | 7 | subscription = source.subscribe( 8 | lambda {|x| 9 | puts 'Next: ' + x.to_s 10 | }, 11 | lambda {|err| 12 | puts 'Error: ' + err.to_s 13 | }, 14 | lambda { 15 | puts 'Completed' 16 | }) 17 | 18 | # => Next: 0 19 | # => Next: 1 20 | # => Next: 1 21 | # => Next: 2 22 | # => Next: 2 23 | # => Next: 2 24 | # => Next: 3 25 | # => Next: 3 26 | # => Next: 4 27 | # => Completed 28 | -------------------------------------------------------------------------------- /examples/multicast.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | subject = Rx::Subject.new 4 | source = Rx::Observable.range(0, 3) 5 | .multicast(subject) 6 | 7 | observer = Rx::Observer.create( 8 | lambda {|x| 9 | puts 'Next: ' + x.to_s 10 | }, 11 | lambda {|err| 12 | puts 'Error: ' + err.to_s 13 | }, 14 | lambda { 15 | puts 'Completed' 16 | } 17 | ) 18 | 19 | subscription = source.subscribe(observer) 20 | subject.subscribe(observer) 21 | 22 | connected = source.connect 23 | 24 | subscription.dispose 25 | 26 | # => Next: 0 27 | # => Next: 0 28 | # => Next: 1 29 | # => Next: 1 30 | # => Next: 2 31 | # => Next: 2 32 | # => Completed 33 | -------------------------------------------------------------------------------- /examples/never.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # This will never produce a value, hence never calling any of the callbacks 4 | source = Rx::Observable.never 5 | 6 | subscription = source.subscribe( 7 | lambda {|x| 8 | puts 'Next: ' + x.to_s 9 | }, 10 | lambda {|err| 11 | puts 'Error: ' + err.to_s 12 | }, 13 | lambda { 14 | puts 'Completed' 15 | }) 16 | -------------------------------------------------------------------------------- /examples/of.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.of(1,2,3) 4 | 5 | subscription = source.subscribe( 6 | lambda {|x| 7 | puts 'Next: ' + x.to_s 8 | }, 9 | lambda {|err| 10 | puts 'Error: ' + err.to_s 11 | }, 12 | lambda { 13 | puts 'Completed' 14 | }) 15 | 16 | # => Next: 1 17 | # => Next: 2 18 | # => Next: 3 19 | # => Completed 20 | -------------------------------------------------------------------------------- /examples/on_error_resume_next.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source1 = Rx::Observable.raise_error(Exception.new('error 1')) 4 | source2 = Rx::Observable.raise_error(Exception.new('error 2')) 5 | source3 = Rx::Observable.return(42) 6 | 7 | source = Rx::Observable.on_error_resume_next(source1, source2, source3) 8 | 9 | subscription = source.subscribe( 10 | lambda {|x| 11 | puts 'Next: ' + x.to_s 12 | }, 13 | lambda {|err| 14 | puts 'Error: ' + err.to_s 15 | }, 16 | lambda { 17 | puts 'Completed' 18 | }) 19 | 20 | # => Next: 42 21 | # => Completed 22 | -------------------------------------------------------------------------------- /examples/pairs.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using Standard JavaScript 4 | obj = { 5 | foo: 42, 6 | bar: 56, 7 | baz: 78 8 | } 9 | 10 | source = Rx::Observable.pairs(obj) 11 | 12 | subscription = source.subscribe( 13 | lambda {|x| 14 | puts 'Next: ' + x.to_s 15 | }, 16 | lambda {|err| 17 | puts 'Error: ' + err.to_s 18 | }, 19 | lambda { 20 | puts 'Completed' 21 | }) 22 | 23 | # => Next: [:foo, 42] 24 | # => Next: [:bar, 56] 25 | # => Next: [:baz, 78] 26 | # => Completed 27 | -------------------------------------------------------------------------------- /examples/publish.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Without publish 4 | interval = Rx::Observable.interval(1) 5 | 6 | source = interval 7 | .take(2) 8 | .do {|x| puts 'Side effect' } 9 | 10 | def create_observer(tag) 11 | Rx::Observer.create( 12 | lambda {|x| 13 | puts 'Next: ' + tag + x.to_s 14 | }, 15 | lambda {|err| 16 | puts 'Error: ' + err.to_s 17 | }, 18 | lambda { 19 | puts 'Completed' 20 | }) 21 | end 22 | 23 | source.subscribe(create_observer('SourceA')) 24 | source.subscribe(create_observer('SourceB')) 25 | 26 | while Thread.list.size > 1 27 | (Thread.list - [Thread.current]).each &:join 28 | end 29 | 30 | # => Side effect 31 | # => Next: SourceA0 32 | # => Side effect 33 | # => Next: SourceB0 34 | # => Side effect 35 | # => Next: SourceA1 36 | # => Completed 37 | # => Side effect 38 | # => Next: SourceB1 39 | # => Completed 40 | 41 | # With publish 42 | interval = Rx::Observable.interval(1) 43 | 44 | source = interval 45 | .take(2) 46 | .do {|x| puts 'Side effect' } 47 | 48 | def create_observer(tag) 49 | Rx::Observer.create( 50 | lambda {|x| 51 | puts 'Next: ' + tag + x.to_s 52 | }, 53 | lambda {|err| 54 | puts 'Error: ' + err.to_s 55 | }, 56 | lambda { 57 | puts 'Completed' 58 | }) 59 | end 60 | 61 | published = source.publish 62 | 63 | published.subscribe(create_observer('SourceA')) 64 | published.subscribe(create_observer('SourceB')) 65 | 66 | connection = published.connect 67 | 68 | # => Side effect 69 | # => Next: SourceA0 70 | # => Next: SourceB0 71 | # => Side effect 72 | # => Next: SourceA1 73 | # => Next: SourceB1 74 | # => Completed 75 | 76 | while Thread.list.size > 1 77 | (Thread.list - [Thread.current]).each &:join 78 | end 79 | 80 | -------------------------------------------------------------------------------- /examples/range.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.range(0, 3) 4 | 5 | subscription = source.subscribe( 6 | lambda {|x| 7 | puts 'Next: ' + x.to_s 8 | }, 9 | lambda {|err| 10 | puts 'Error: ' + err.to_s 11 | }, 12 | lambda { 13 | puts 'Completed' 14 | }) 15 | 16 | # => Next: 0 17 | # => Next: 1 18 | # => Next: 2 19 | # => Completed 20 | -------------------------------------------------------------------------------- /examples/reduce.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.range(1, 3) 4 | .reduce(1) {|acc, x| acc * x } 5 | 6 | subscription = source.subscribe( 7 | lambda {|x| 8 | puts 'Next: ' + x.to_s 9 | }, 10 | lambda {|err| 11 | puts 'Error: ' + err.to_s 12 | }, 13 | lambda { 14 | puts 'Completed' 15 | }) 16 | 17 | # => Next: 6 18 | # => Completed 19 | -------------------------------------------------------------------------------- /examples/repeat.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.repeat(42, 3) 4 | 5 | subscription = source.subscribe( 6 | lambda {|x| 7 | puts 'Next: ' + x.to_s 8 | }, 9 | lambda {|err| 10 | puts 'Error: ' + err.to_s 11 | }, 12 | lambda { 13 | puts 'Completed' 14 | }) 15 | 16 | #=> Next: 42 17 | # => Next: 42 18 | # => Next: 42 19 | # => Completed 20 | -------------------------------------------------------------------------------- /examples/return.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.just(42) 4 | 5 | subscription = source.subscribe( 6 | lambda {|x| 7 | puts 'Next: ' + x.to_s 8 | }, 9 | lambda {|err| 10 | puts 'Error: ' + err.to_s 11 | }, 12 | lambda { 13 | puts 'Completed' 14 | }) 15 | 16 | # => Next: 42 17 | # => Completed 18 | -------------------------------------------------------------------------------- /examples/sample.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # With an interval time 4 | source = Rx::Observable.interval(0.05) 5 | .delay(0.01) 6 | .sample(0.15) 7 | .take(2) 8 | 9 | source.subscribe( 10 | lambda { |x| 11 | puts 'Next: ' + x.to_s 12 | }, 13 | lambda { |err| 14 | puts 'Error: ' + err.inspect 15 | }, 16 | lambda { 17 | puts 'Completed' 18 | }) 19 | 20 | # => Next: 1 21 | # => Next: 4 22 | # => Completed 23 | 24 | while Thread.list.size > 1 25 | (Thread.list - [Thread.current]).each(&:join) 26 | end 27 | 28 | # With a sampler 29 | source = Rx::Observable.interval(0.05) 30 | .sample(Rx::Observable.interval(0.15).delay(0.01)) 31 | .take(2) 32 | 33 | source.subscribe( 34 | lambda { |x| 35 | puts 'Next: ' + x.to_s 36 | }, 37 | lambda { |err| 38 | puts 'Error: ' + err.inspect 39 | }, 40 | lambda { 41 | puts 'Completed' 42 | }) 43 | 44 | # => Next: 2 45 | # => Next: 5 46 | # => Completed 47 | 48 | while Thread.list.size > 1 49 | (Thread.list - [Thread.current]).each(&:join) 50 | end 51 | -------------------------------------------------------------------------------- /examples/scan.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Without a seed 4 | source = Rx::Observable.range(1, 3) 5 | .scan {|acc, x| acc + x } 6 | 7 | subscription = source.subscribe( 8 | lambda {|x| 9 | puts 'Next: ' + x.to_s 10 | }, 11 | lambda {|err| 12 | puts 'Error: ' + err.to_s 13 | }, 14 | lambda { 15 | puts 'Completed' 16 | }) 17 | 18 | # => Next: 1 19 | # => Next: 3 20 | # => Next: 6 21 | # => Completed 22 | 23 | # With a seed 24 | source = Rx::Observable.range(1, 3) 25 | .scan(1) {|acc, x| acc * x } 26 | 27 | subscription = source.subscribe( 28 | lambda {|x| 29 | puts 'Next: ' + x.to_s 30 | }, 31 | lambda {|err| 32 | puts 'Error: ' + err.to_s 33 | }, 34 | lambda { 35 | puts 'Completed' 36 | }) 37 | 38 | # => Next: 1 39 | # => Next: 2 40 | # => Next: 6 41 | # => Completed 42 | -------------------------------------------------------------------------------- /examples/start.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | context = { value: 42 } 4 | 5 | source = Rx::Observable.start( 6 | lambda { 7 | return self[:value] 8 | }, 9 | context, 10 | Rx::DefaultScheduler.instance 11 | ) 12 | 13 | subscription = source.subscribe( 14 | lambda {|x| 15 | puts 'Next: ' + x.to_s 16 | }, 17 | lambda {|err| 18 | puts 'Error: ' + err.to_s 19 | }, 20 | lambda { 21 | puts 'Completed' 22 | }) 23 | 24 | # => Next: 42 25 | # => Completed 26 | 27 | while Thread.list.size > 1 28 | (Thread.list - [Thread.current]).each &:join 29 | end 30 | -------------------------------------------------------------------------------- /examples/throw.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.return(42) 4 | .flat_map { Rx::Observable.raise_error(Exception.new('error!')) } 5 | 6 | subscription = source.subscribe( 7 | lambda {|x| 8 | puts 'Next: ' + x.to_s 9 | }, 10 | lambda {|err| 11 | puts 'Error: ' + err.to_s 12 | }, 13 | lambda { 14 | puts 'Completed' 15 | }) 16 | 17 | # => Error: error! 18 | -------------------------------------------------------------------------------- /examples/time_intervals.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.timer(0, 1) 4 | .time_interval 5 | .map {|x| x.value.to_s + ":" + x.interval.to_s } 6 | .take(5) 7 | 8 | subscription = source.subscribe( 9 | lambda {|x| 10 | puts 'Next: ' + x.to_s 11 | }, 12 | lambda {|err| 13 | puts 'Error: ' + err.to_s 14 | }, 15 | lambda { 16 | puts 'Completed' 17 | }) 18 | 19 | # => Next: 0:0.0000000 20 | # => Next: 1:1.0000000 21 | # => Next: 2:1.0000000 22 | # => Next: 3:1.0000000 23 | # => Next: 4:1.0000000 24 | # => Completed 25 | 26 | while Thread.list.size > 1 27 | (Thread.list - [Thread.current]).each &:join 28 | end 29 | -------------------------------------------------------------------------------- /examples/timer.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.timer(0.2, 0.1) 4 | .time_interval 5 | .pluck('interval') 6 | .take(3) 7 | 8 | subscription = source.subscribe( 9 | lambda {|x| 10 | puts 'Next: ' + x.to_s 11 | }, 12 | lambda {|err| 13 | puts 'Error: ' + err.to_s 14 | }, 15 | lambda { 16 | puts 'Completed' 17 | }) 18 | 19 | # => Next: 0.2 20 | # => Next: 0.1 21 | # => Next: 0.1 22 | # => Completed 23 | 24 | while Thread.list.size > 1 25 | (Thread.list - [Thread.current]).each &:join 26 | end 27 | -------------------------------------------------------------------------------- /examples/timestamp.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.timer(0, 1) 4 | .timestamp 5 | .map {|x| x[:value].to_s + ':' + x[:timestamp].to_i.to_s } 6 | .take(5) 7 | 8 | subscription = source.subscribe( 9 | lambda {|x| 10 | puts 'Next: ' + x.to_s 11 | }, 12 | lambda {|err| 13 | puts 'Error: ' + err.to_s 14 | }, 15 | lambda { 16 | puts 'Completed' 17 | }) 18 | 19 | # => Next: 0:1378690776 20 | # => Next: 1:1378690777 21 | # => Next: 2:1378690778 22 | # => Next: 3:1378690779 23 | # => Next: 4:1378690780 24 | # => Completed 25 | 26 | while Thread.list.size > 1 27 | (Thread.list - [Thread.current]).each &:join 28 | end 29 | -------------------------------------------------------------------------------- /examples/to_a.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | source = Rx::Observable.timer(0, 0.1) 4 | .take(5) 5 | .to_a 6 | 7 | subscription = source.subscribe( 8 | lambda {|x| 9 | puts 'Next: ' + x.to_s 10 | }, 11 | lambda {|err| 12 | puts 'Error: ' + err.to_s 13 | }, 14 | lambda { 15 | puts 'Completed' 16 | }) 17 | 18 | # => Next: [0, 1, 2, 3, 4] 19 | # => Completed 20 | 21 | while Thread.list.size > 1 22 | (Thread.list - [Thread.current]).each &:join 23 | end 24 | -------------------------------------------------------------------------------- /examples/to_async.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | func = Rx::Observable.to_async(lambda {|x, y| 4 | return x + y 5 | }) 6 | 7 | # Execute function with 3 and 4 8 | source = func.call(3, 4) 9 | 10 | subscription = source.subscribe( 11 | lambda {|x| 12 | puts 'Next: ' + x.to_s 13 | }, 14 | lambda {|err| 15 | puts 'Error: ' + err.to_s 16 | }, 17 | lambda { 18 | puts 'Completed' 19 | }) 20 | 21 | # => Next: 7 22 | # => Completed 23 | 24 | while Thread.list.size > 1 25 | (Thread.list - [Thread.current]).each &:join 26 | end 27 | -------------------------------------------------------------------------------- /examples/using.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using an AsyncSubject as a resource which supports the .dispose method 4 | class DisposableResource 5 | def initialize(value, disposed = false) 6 | @value = value 7 | @disposed = disposed 8 | end 9 | 10 | def value 11 | if @disposed 12 | throw Exception.new('Object is disposed') 13 | end 14 | @value 15 | end 16 | 17 | def unsubscribe 18 | unless @disposed 19 | @disposed = true 20 | @value = nil 21 | end 22 | puts 'Disposed' 23 | end 24 | end 25 | 26 | source = Rx::Observable.using( 27 | lambda { return DisposableResource.new(42) }, 28 | lambda {|resource| 29 | subject = Rx::AsyncSubject.new 30 | subject.on_next(resource.value) 31 | subject.on_completed 32 | return subject 33 | } 34 | ) 35 | 36 | subscription = source.subscribe( 37 | lambda {|x| 38 | puts 'Next: ' + x.to_s 39 | }, 40 | lambda {|err| 41 | puts 'Error: ' + err.to_s 42 | }, 43 | lambda { 44 | puts 'Completed' 45 | }) 46 | 47 | # => Next: 42 48 | # => Completed 49 | 50 | subscription.dispose 51 | 52 | # => Disposed 53 | -------------------------------------------------------------------------------- /examples/when.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Fire each plan when both are ready 4 | source = Rx::Observable.when( 5 | Rx::Observable.timer(0.1).and(Rx::Observable.timer(0.5)).then_do(lambda {|x, y| return 'first' }), 6 | Rx::Observable.timer(0.4).and(Rx::Observable.timer(0.3)).then_do {|x, y| 'second' } 7 | ) 8 | 9 | subscription = source.subscribe( 10 | lambda {|x| 11 | puts 'Next: ' + x.to_s 12 | }, 13 | lambda {|err| 14 | puts 'Error: ' + err.to_s 15 | }, 16 | lambda { 17 | puts 'Completed' 18 | }) 19 | 20 | # => Next: second 21 | # => Next: first 22 | # => Completed 23 | 24 | while Thread.list.size > 1 25 | (Thread.list - [Thread.current]).each &:join 26 | end 27 | -------------------------------------------------------------------------------- /examples/while.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | i = 0 4 | 5 | # Repeat until condition no longer holds 6 | source = Rx::Observable.while( 7 | lambda { i += 1; i <= 3 }, 8 | Rx::Observable.return(42) 9 | ) 10 | 11 | subscription = source.subscribe( 12 | lambda {|x| 13 | puts 'Next: ' + x.to_s 14 | }, 15 | lambda {|err| 16 | puts 'Error: ' + err.to_s 17 | }, 18 | lambda { 19 | puts 'Completed' 20 | }) 21 | 22 | # => Next: 42 23 | # => Next: 42 24 | # => Next: 42 25 | # => Completed 26 | -------------------------------------------------------------------------------- /examples/window_with_time.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Without a skip 4 | source = Rx::Observable.interval(0.1) 5 | .window_with_time(0.5) 6 | .take(3) 7 | 8 | subscription = source.subscribe( 9 | lambda {|child| 10 | child.to_a.subscribe( 11 | lambda {|x| 12 | puts 'Child Next: ' + x.to_s 13 | }, 14 | lambda {|err| 15 | puts 'Child Error: ' + err.to_s 16 | }, 17 | lambda { 18 | puts 'Child Completed' 19 | } 20 | ) 21 | }, 22 | lambda {|err| 23 | puts 'Error: ' + err.to_s 24 | }, 25 | lambda { 26 | puts 'Completed' 27 | }) 28 | 29 | # => Child Next: [0, 1, 2, 3] 30 | # => Child Completed 31 | # => Completed 32 | # => Child Next: [4, 5, 6, 7, 8] 33 | # => Child Completed 34 | # => Child Next: [9, 10, 11, 12, 13] 35 | # => Child Completed 36 | 37 | while Thread.list.size > 1 38 | (Thread.list - [Thread.current]).each &:join 39 | end 40 | 41 | # Using a skip 42 | source = Rx::Observable.interval(0.1) 43 | .window_with_time(0.5, 0.1) 44 | .take(3) 45 | 46 | subscription = source.subscribe( 47 | lambda {|child| 48 | 49 | child.to_a.subscribe( 50 | lambda {|x| 51 | puts 'Child Next: ' + x.to_s 52 | }, 53 | lambda {|err| 54 | puts 'Child Error: ' + err.to_s 55 | }, 56 | lambda { 57 | puts 'Child Completed' 58 | } 59 | ) 60 | }, 61 | lambda {|err| 62 | puts 'Error: ' + err.to_s 63 | }, 64 | lambda { 65 | puts 'Completed' 66 | }) 67 | 68 | # => Completed 69 | # => Child Next: [0, 1, 2, 3, 4] 70 | # => Child Completed 71 | # => Child Next: [1, 2, 3, 4, 5] 72 | # => Child Completed 73 | # => Child Next: [2, 3, 4, 5, 6] 74 | # => Child Completed 75 | 76 | while Thread.list.size > 1 77 | (Thread.list - [Thread.current]).each &:join 78 | end 79 | -------------------------------------------------------------------------------- /examples/zip.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | # Using arguments 4 | range = Rx::Observable.range(0, 5) 5 | 6 | source = Rx::Observable.zip( 7 | range, 8 | range.skip(1), 9 | range.skip(2)) {|s1, s2, s3| 10 | s1.to_s + ':' + s2.to_s + ':' + s3.to_s 11 | } 12 | 13 | subscription = source.subscribe( 14 | lambda {|x| 15 | puts 'Next: ' + x.to_s 16 | }, 17 | lambda {|err| 18 | puts 'Error: ' + err.to_s 19 | }, 20 | lambda { 21 | puts 'Completed' 22 | }) 23 | 24 | # => Next: 0:1:2 25 | # => Next: 1:2:3 26 | # => Next: 2:3:4 27 | # => Completed 28 | -------------------------------------------------------------------------------- /examples/zip_array.rb: -------------------------------------------------------------------------------- 1 | require 'rx' 2 | 3 | range = Rx::Observable.range(0, 5) 4 | 5 | source = Rx::Observable.zip( 6 | range, 7 | range.skip(1), 8 | range.skip(2) 9 | ) 10 | 11 | subscription = source.subscribe( 12 | lambda {|x| 13 | puts 'Next: ' + x.to_s 14 | }, 15 | lambda {|err| 16 | puts 'Error: ' + err.to_s 17 | }, 18 | lambda { 19 | puts 'Completed' 20 | }) 21 | 22 | # => Next: [0, 1, 2] 23 | # => Next: [1, 2, 3] 24 | # => Next: [2, 3, 4] 25 | # => Completed 26 | -------------------------------------------------------------------------------- /lib/core_ext/enumerable.rb: -------------------------------------------------------------------------------- 1 | module Enumerable 2 | def subscribe(observer, scheduler = Rx::ImmediateScheduler.instance) 3 | begin 4 | self.each do |e| 5 | scheduler.schedule lambda { 6 | observer.on_next(e) 7 | } 8 | end 9 | rescue => ex 10 | observer.on_error(ex) 11 | return 12 | end 13 | 14 | observer.on_completed 15 | end 16 | 17 | def to_observable(scheduler = Rx::ImmediateScheduler.instance) 18 | Rx::AnonymousObservable.new do |observer| 19 | self.subscribe(observer, scheduler) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/rx.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed 4 | 5 | # Require all of the Ruby files in the given directory. 6 | # 7 | # path - The String relative path from here to the directory. 8 | # 9 | # Returns nothing. 10 | def require_all(path) 11 | glob = File.join(File.dirname(__FILE__), path, '*.rb') 12 | Dir[glob].sort.each do |f| 13 | require f 14 | end 15 | end 16 | 17 | require_all 'rx/internal/' 18 | require_all 'rx/concurrency/' 19 | require_all 'rx/subscriptions/' 20 | require_all 'rx/core/' 21 | require_all 'rx/linq/' 22 | require_all 'rx/linq/observable/' 23 | require_all 'rx/operators/' 24 | require_all 'rx/subjects/' 25 | require_all 'rx/testing/' 26 | require_all 'rx/joins/' 27 | require_all 'rx/' 28 | -------------------------------------------------------------------------------- /lib/rx/concurrency/async_lock.rb: -------------------------------------------------------------------------------- 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 | 5 | module Rx 6 | # Asynchronous lock. 7 | class AsyncLock 8 | 9 | def initialize 10 | @queue = [] 11 | @is_acquired = false 12 | @has_faulted = false 13 | @gate = Mutex.new 14 | end 15 | 16 | def wait(&action) 17 | @gate.synchronize do 18 | @queue.push action unless @has_faulted 19 | 20 | if @is_acquired or @has_faulted 21 | return 22 | else 23 | @is_acquired = true 24 | end 25 | end 26 | 27 | loop do 28 | work = nil 29 | 30 | @gate.synchronize do 31 | work = @queue.shift 32 | 33 | unless work 34 | @is_acquired = false 35 | return 36 | end 37 | end 38 | 39 | begin 40 | work.call 41 | rescue 42 | clear 43 | raise 44 | end 45 | end 46 | end 47 | 48 | # Clears the work items in the queue and drops further work being queued. 49 | def clear 50 | @gate.synchronize do 51 | @queue = [] 52 | @has_faulted = true 53 | end 54 | end 55 | 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/rx/concurrency/current_thread_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'singleton' 4 | require 'thread' 5 | require 'rx/internal/priority_queue' 6 | require 'rx/concurrency/local_scheduler' 7 | require 'rx/concurrency/scheduled_item' 8 | require 'rx/subscriptions/subscription' 9 | 10 | module Rx 11 | 12 | # Represents an object that schedules units of work on the platform's default scheduler. 13 | class CurrentThreadScheduler < Rx::LocalScheduler 14 | 15 | include Singleton 16 | 17 | @@thread_local_queue = nil 18 | 19 | # Gets a value that indicates whether the caller must call a Schedule method. 20 | def self.schedule_required? 21 | @@thread_local_queue.nil? 22 | end 23 | 24 | # Schedules an action to be executed after dueTime. 25 | def schedule_relative_with_state(state, due_time, action) 26 | raise 'action cannot be nil' unless action 27 | 28 | dt = self.now.to_i + Scheduler.normalize(due_time) 29 | si = ScheduledItem.new self, state, dt, &action 30 | 31 | local_queue = self.class.queue 32 | 33 | unless local_queue 34 | local_queue = PriorityQueue.new 35 | local_queue.push si 36 | 37 | self.class.queue = local_queue 38 | 39 | begin 40 | self.class.run_trampoline local_queue 41 | ensure 42 | self.class.queue = nil 43 | end 44 | else 45 | local_queue.push si 46 | end 47 | 48 | Subscription.create { si.cancel } 49 | end 50 | 51 | private 52 | 53 | class << self 54 | def queue 55 | @@thread_local_queue 56 | end 57 | 58 | def queue=(new_queue) 59 | @@thread_local_queue = new_queue 60 | end 61 | 62 | def run_trampoline(queue) 63 | while item = queue.shift 64 | unless item.cancelled? 65 | wait = item.due_time - Scheduler.now.to_i 66 | sleep wait if wait > 0 67 | item.invoke unless item.cancelled? 68 | end 69 | end 70 | end 71 | 72 | end 73 | 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /lib/rx/concurrency/default_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'singleton' 4 | require 'thread' 5 | require 'rx/concurrency/local_scheduler' 6 | require 'rx/concurrency/periodic_scheduler' 7 | require 'rx/subscriptions/subscription' 8 | require 'rx/subscriptions/single_assignment_subscription' 9 | require 'rx/subscriptions/composite_subscription' 10 | 11 | module Rx 12 | 13 | # Represents an object that schedules units of work on the platform's default scheduler. 14 | class DefaultScheduler < Rx::LocalScheduler 15 | 16 | include Singleton 17 | include Rx::PeriodicScheduler 18 | 19 | # Schedules an action to be executed. 20 | def schedule_with_state(state, action) 21 | raise 'action cannot be nil' unless action 22 | 23 | d = SingleAssignmentSubscription.new 24 | 25 | t = Thread.new do 26 | d.subscription = action.call self, state unless d.unsubscribed? 27 | end 28 | 29 | CompositeSubscription.new [d, Subscription.create { t.exit }] 30 | end 31 | 32 | # Schedules an action to be executed after dueTime 33 | def schedule_relative_with_state(state, due_time, action) 34 | raise 'action cannot be nil' unless action 35 | 36 | dt = Scheduler.normalize due_time 37 | return self.schedule_with_state state, action if dt == 0 38 | 39 | d = SingleAssignmentSubscription.new 40 | 41 | t = Thread.new do 42 | sleep dt 43 | Thread.new { 44 | d.subscription = action.call self, state unless d.unsubscribed? 45 | } 46 | end 47 | 48 | CompositeSubscription.new [d, Subscription.create { t.exit }] 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/rx/concurrency/historical_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'rx/concurrency/virtual_time_scheduler' 4 | require 'rx/internal/priority_queue' 5 | 6 | module Rx 7 | 8 | # Provides a virtual time scheduler that uses Time for absolute time and Number for relative time. 9 | class HistoricalScheduler < VirtualTimeScheduler 10 | 11 | def initialize(clock = Time.at(0)) 12 | super 13 | @clock = clock 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /lib/rx/concurrency/immediate_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'singleton' 4 | require 'thread' 5 | require 'rx/concurrency/local_scheduler' 6 | require 'rx/subscriptions/single_assignment_subscription' 7 | 8 | module Rx 9 | 10 | # Represents an object that schedules units of work to run immediately on the current thread. 11 | class ImmediateScheduler < LocalScheduler 12 | 13 | include Singleton 14 | 15 | # Schedules an action to be executed. 16 | def schedule_with_state(state, action) 17 | raise ArgumentError.new 'action cannot be nil' unless action 18 | action.call AsyncLockScheduler.new, state 19 | end 20 | 21 | def schedule_relative_with_state(state, due_time, action) 22 | raise ArgumentError.new 'action cannot be nil' unless action 23 | 24 | dt = Rx::Scheduler.normalize due_time 25 | sleep dt if dt > 0 26 | action.call AsyncLockScheduler.new, state 27 | end 28 | 29 | private 30 | 31 | class AsyncLockScheduler < LocalScheduler 32 | 33 | def initialize 34 | @gate = nil 35 | end 36 | 37 | def schedule_with_state(state, action) 38 | m = SingleAssignmentSubscription.new 39 | 40 | @gate = AsyncLock.new if @gate.nil? 41 | 42 | @gate.wait do 43 | m.subscription = action.call self, state unless m.unsubscribed? 44 | end 45 | 46 | m 47 | end 48 | 49 | def schedule_relative_with_state(state, due_time, action) 50 | return self.schedule_with_state state, action if due_time <= 0 51 | 52 | m = SingleAssignmentSubscription.new 53 | 54 | timer = Time.new 55 | 56 | @gate = AsyncLock.new if @gate.nil? 57 | 58 | @gate.wait do 59 | sleep_time = Time.new - timer 60 | sleep sleep_time if sleep_time > 0 61 | m.subscription = action.call self, state unless m.unsubscribed? 62 | end 63 | 64 | m 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /lib/rx/concurrency/local_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'rx/concurrency/scheduler' 4 | 5 | module Rx 6 | # Abstract base class for machine-local schedulers, using the local system clock for time-based operations. 7 | class LocalScheduler 8 | 9 | include Scheduler 10 | 11 | # Gets the scheduler's notion of current time. 12 | def now 13 | Time.now 14 | end 15 | 16 | # Schedules an action to be executed. 17 | def schedule_with_state(state, action) 18 | raise 'action cannot be nil' unless action 19 | 20 | schedule_relative_with_state(state, 0, action) 21 | end 22 | 23 | # Schedules an action to be executed at dueTime. 24 | def schedule_absolute_with_state(state, due_time, action) 25 | raise 'action cannot be nil' unless action 26 | 27 | schedule_relative_with_state(state, (due_time - self.now), action) 28 | end 29 | 30 | def schedule_relative_with_state(state, due_time, action) 31 | raise ArgumentError.new 'action cannot be nil' unless action 32 | 33 | dt = Rx::Scheduler.normalize due_time 34 | sleep dt if dt > 0 35 | action.call(self, state) 36 | end 37 | 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/rx/concurrency/periodic_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | module Rx 4 | 5 | # Provides periodic scheduling capabilities 6 | module PeriodicScheduler 7 | 8 | # Schedules a periodic piece of work by dynamically discovering the scheduler's capabilities. 9 | def schedule_periodic(period, action) 10 | raise 'action cannot be nil' unless action 11 | raise 'period cannot be less than zero' if period < 0 12 | 13 | self.schedule_periodic_with_state(action, period, lambda {|a| 14 | a.call 15 | return a 16 | }) 17 | end 18 | 19 | # Schedules a periodic piece of work 20 | def schedule_periodic_with_state(state, due_time, action) 21 | raise 'action cannot be nil' unless action 22 | raise 'due_time cannot be less than zero' if due_time < 0 23 | 24 | state1 = state 25 | gate = Mutex.new 26 | 27 | PeriodicTimer.new due_time do 28 | gate.synchronize do 29 | state1 = action.call state1 30 | end 31 | end 32 | end 33 | 34 | private 35 | 36 | # Internal timer 37 | class PeriodicTimer 38 | def initialize(seconds, &action) 39 | @seconds = seconds 40 | @unsubscribed = false 41 | @gate = Mutex.new 42 | 43 | self.run_loop(&action) 44 | end 45 | 46 | def unsubscribe 47 | @gate.synchronize do 48 | @unsubscribed = true unless @unsubscribed 49 | end 50 | end 51 | 52 | def time_block 53 | start_time = Time.new 54 | yield 55 | Time.new - start_time 56 | end 57 | 58 | def run_loop 59 | Thread.new do 60 | should_run = true 61 | 62 | elapsed = 0 63 | while should_run 64 | sleep @seconds - elapsed 65 | elapsed = time_block { yield } 66 | @gate.synchronize do 67 | should_run = !@unsubscribed 68 | end 69 | end 70 | end 71 | end 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /lib/rx/concurrency/scheduled_item.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'rx/subscriptions/single_assignment_subscription' 4 | 5 | module Rx 6 | 7 | # Represents a scheduled work item based on the materialization of an scheduler.schedule method call. 8 | class ScheduledItem 9 | 10 | include Comparable 11 | 12 | attr_reader :due_time 13 | 14 | def initialize(scheduler, state, due_time, &action) 15 | @scheduler = scheduler 16 | @state = state 17 | @action = action 18 | @due_time = due_time 19 | @subscription = SingleAssignmentSubscription.new 20 | end 21 | 22 | # Gets whether the work item has received a cancellation request. 23 | def cancelled? 24 | @subscription.unsubscribed? 25 | end 26 | 27 | # Invokes the work item. 28 | def invoke 29 | @subscription.subscription = @action.call @scheduler, @state unless @subscription.unsubscribed? 30 | end 31 | 32 | def <=>(other) 33 | return @due_time <=> other.due_time 34 | end 35 | 36 | # Cancels the work item by disposing the resource returned by invoke_core as soon as possible. 37 | def cancel 38 | @subscription.unsubscribe 39 | end 40 | 41 | end 42 | end -------------------------------------------------------------------------------- /lib/rx/core/async_lock_observer.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'rx/concurrency/async_lock' 4 | require 'rx/core/observer' 5 | 6 | module Rx 7 | 8 | module Observer 9 | 10 | class << self 11 | # Synchronizes access to the observer such that its callback methods cannot be called concurrently, using the specified asynchronous lock to protect against concurrent and reentrant access. 12 | # This overload is useful when coordinating multiple observers that access shared state by synchronizing on a common asynchronous lock. 13 | def prevent_reentrancy(observer, gate = AsyncLock.new) 14 | AsyncLockObserver.new(observer, gate) 15 | end 16 | end 17 | end 18 | 19 | class AsyncLockObserver < Rx::ObserverBase 20 | 21 | def on_next_core(value) 22 | @gate.wait { @observer.on_next value } 23 | end 24 | 25 | def on_error_core(error) 26 | @gate.wait { @observer.on_error error } 27 | end 28 | 29 | def on_completed_core 30 | @gate.wait { @observer.on_completed } 31 | end 32 | 33 | def initialize(observer, gate) 34 | @observer = observer 35 | @gate = gate 36 | 37 | config = ObserverConfiguration.new 38 | config.on_next(&method(:on_next_core)) 39 | config.on_error(&method(:on_error_core)) 40 | config.on_completed(&method(:on_completed_core)) 41 | 42 | super(config) 43 | end 44 | 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/rx/core/auto_detach_observer.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'rx/core/observer' 4 | require 'rx/subscriptions/single_assignment_subscription' 5 | 6 | module Rx 7 | 8 | class AutoDetachObserver < Rx::ObserverBase 9 | 10 | def on_next_core(value) 11 | no_error = false 12 | begin 13 | @observer.on_next(value) 14 | no_error = true 15 | ensure 16 | unsubscribe unless no_error 17 | end 18 | end 19 | 20 | def on_error_core(error) 21 | begin 22 | @observer.on_error(error) 23 | ensure 24 | unsubscribe 25 | end 26 | end 27 | 28 | def on_completed_core 29 | begin 30 | @observer.on_completed 31 | ensure 32 | unsubscribe 33 | end 34 | end 35 | 36 | def initialize(observer) 37 | @observer = observer 38 | @m = SingleAssignmentSubscription.new 39 | 40 | config = ObserverConfiguration.new 41 | config.on_next(&method(:on_next_core)) 42 | config.on_error(&method(:on_error_core)) 43 | config.on_completed(&method(:on_completed_core)) 44 | 45 | super(config) 46 | end 47 | 48 | def subscription=(new_subscription) 49 | @m.subscription = new_subscription 50 | end 51 | 52 | def unsubscribe 53 | super 54 | @m.unsubscribe 55 | end 56 | 57 | end 58 | end 59 | 60 | -------------------------------------------------------------------------------- /lib/rx/core/checked_observer.rb: -------------------------------------------------------------------------------- 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/core/observer' 5 | 6 | module Rx 7 | 8 | module Observer 9 | # Checks access to the observer for grammar violations. This includes checking for multiple on_error or on_completed calls, as well as reentrancy in any of the observer methods. 10 | # If a violation is detected, an error is thrown from the offending observer method call. 11 | def checked 12 | CheckedObserver.new(self) 13 | end 14 | end 15 | 16 | class CheckedObserver 17 | include Observer 18 | 19 | def initialize(observer) 20 | @observer = observer 21 | @state = :idle 22 | end 23 | 24 | def on_next(value) 25 | check_access 26 | begin 27 | @observer.on_next value 28 | ensure 29 | Mutex.new.synchronize { @state = :idle } 30 | end 31 | end 32 | 33 | def on_error(error) 34 | check_access 35 | begin 36 | @observer.on_error error 37 | ensure 38 | Mutex.new.synchronize { @state = :done } 39 | end 40 | end 41 | 42 | def on_completed 43 | check_access 44 | begin 45 | @observer.on_completed 46 | ensure 47 | Mutex.new.synchronize { @state = :done } 48 | end 49 | end 50 | 51 | private 52 | 53 | def check_access 54 | Mutex.new.synchronize do 55 | old = @state 56 | @state = :busy if @state == :idle 57 | case old 58 | when :busy 59 | raise 'Re-entrancy detected' 60 | when :done 61 | raise 'Observer terminated' 62 | end 63 | end 64 | end 65 | end 66 | end -------------------------------------------------------------------------------- /lib/rx/core/observable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'rx/concurrency/current_thread_scheduler' 4 | require 'rx/concurrency/immediate_scheduler' 5 | require 'rx/core/observer' 6 | require 'rx/core/auto_detach_observer' 7 | require 'rx/subscriptions/subscription' 8 | 9 | module Rx 10 | 11 | module Observable 12 | 13 | def subscribe(*args) 14 | case args.size 15 | when 0 16 | if block_given? 17 | _subscribe Observer.configure {|o| o.on_next(&Proc.new) } 18 | else 19 | _subscribe Observer.configure 20 | end 21 | when 1 22 | _subscribe args[0] 23 | when 3 24 | _subscribe Observer.configure {|o| 25 | o.on_next(&args[0]) 26 | o.on_error(&args[1]) 27 | o.on_completed(&args[2]) 28 | } 29 | else 30 | raise ArgumentError, "wrong number of arguments (#{args.size} for 0..1 or 3)" 31 | end 32 | end 33 | 34 | # Subscribes the given observer to the observable sequence. 35 | # @param [Observer] observer 36 | # @return [Subscription] 37 | def _subscribe(observer) 38 | 39 | auto_detach_observer = AutoDetachObserver.new observer 40 | 41 | if CurrentThreadScheduler.schedule_required? 42 | CurrentThreadScheduler.instance.schedule_with_state auto_detach_observer, method(:schedule_subscribe) 43 | else 44 | begin 45 | auto_detach_observer.subscription = subscribe_core auto_detach_observer 46 | rescue => e 47 | raise e unless auto_detach_observer.fail e 48 | end 49 | end 50 | 51 | auto_detach_observer 52 | end 53 | 54 | # Subscribes the given block to the on_next action of the observable sequence. 55 | # @param [Object] block 56 | # @return [Subscription] 57 | def subscribe_on_next(&block) 58 | raise ArgumentError.new 'Block is required' unless block_given? 59 | subscribe(Observer.configure {|o| o.on_next(&block) }) 60 | end 61 | 62 | # Subscribes the given block to the on_error action of the observable sequence. 63 | def subscribe_on_error(&block) 64 | raise ArgumentError.new 'Block is required' unless block_given? 65 | subscribe(Observer.configure {|o| o.on_error(&block) }) 66 | end 67 | 68 | # Subscribes the given block to the on_completed action of the observable sequence. 69 | def subscribe_on_completed(&block) 70 | raise ArgumentError.new 'Block is required' unless block_given? 71 | subscribe(Observer.configure {|o| o.on_completed(&block) }) 72 | end 73 | 74 | private 75 | 76 | def schedule_subscribe(_, auto_detach_observer) 77 | begin 78 | auto_detach_observer.subscription = subscribe_core auto_detach_observer 79 | rescue => e 80 | raise e unless auto_detach_observer.fail e 81 | end 82 | 83 | Subscription.empty 84 | end 85 | 86 | end 87 | 88 | class AnonymousObservable 89 | 90 | include Observable 91 | 92 | def initialize(&subscribe) 93 | @subscribe = subscribe 94 | end 95 | 96 | protected 97 | 98 | def subscribe_core(obs) 99 | @subscribe.call(obs) || Subscription.empty 100 | end 101 | 102 | end 103 | 104 | end 105 | -------------------------------------------------------------------------------- /lib/rx/core/observe_on_observer.rb: -------------------------------------------------------------------------------- 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/core/scheduled_observer' 5 | 6 | module Rx 7 | 8 | module Observer 9 | # Schedules the invocation of observer methods on the given scheduler. 10 | def notify_on(scheduler) 11 | ObserveOnObserver.new(scheduler, self, nil) 12 | end 13 | end 14 | 15 | class ObserveOnObserver < ScheduledObserver 16 | 17 | def initialize(scheduler, observer, cancel = nil) 18 | @cancel = cancel 19 | 20 | super(scheduler, observer) 21 | end 22 | 23 | def on_next_core(value) 24 | ensure_active 25 | super(value) 26 | end 27 | 28 | def on_error_core(error) 29 | ensure_active 30 | super(error) 31 | end 32 | 33 | def on_completed_core 34 | ensure_active 35 | super 36 | end 37 | 38 | def unsubscribe 39 | super 40 | 41 | cancel = nil 42 | Mutex.new.synchronize do 43 | cancel = @cancel 44 | @cancel = nil 45 | end 46 | 47 | canel.unsubscribe if cancel 48 | end 49 | end 50 | end -------------------------------------------------------------------------------- /lib/rx/core/observer.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | module Rx 4 | 5 | # Configuration class for storing Observer actions 6 | class ObserverConfiguration 7 | 8 | DEFAULT_ON_NEXT = lambda {|x| } 9 | DEFAULT_ON_ERROR = lambda {|error| raise error } 10 | DEFAULT_ON_COMPLETED = lambda { } 11 | 12 | attr_reader :on_next_action, :on_error_action, :on_completed_action 13 | 14 | def initialize 15 | @on_next_action = DEFAULT_ON_NEXT 16 | @on_error_action = DEFAULT_ON_ERROR 17 | @on_completed_action = DEFAULT_ON_COMPLETED 18 | end 19 | 20 | def on_next(&on_next_action) 21 | @on_next_action = on_next_action 22 | end 23 | 24 | def on_error(&on_error_action) 25 | @on_error_action = on_error_action 26 | end 27 | 28 | def on_completed(&on_completed_action) 29 | @on_completed_action = on_completed_action 30 | end 31 | end 32 | 33 | # Module for all Observers 34 | module Observer 35 | 36 | # Hides the identity of an observer. 37 | def as_observer 38 | Observer.configure do |o| 39 | o.on_next(&method(:on_next)) 40 | o.on_error(&method(:on_error)) 41 | o.on_completed(&method(:on_completed)) 42 | end 43 | end 44 | 45 | # Creates a notification callback from an observer. 46 | def to_notifier 47 | lambda {|n| n.accept self} 48 | end 49 | 50 | class << self 51 | 52 | # Configures a new instance of an Observer 53 | def configure 54 | config = ObserverConfiguration.new 55 | yield config if block_given? 56 | ObserverBase.new config 57 | end 58 | 59 | def create(on_next = nil, on_error = nil, on_completed = nil) 60 | configure do |o| 61 | o.on_next(&on_next) if on_next 62 | o.on_error(&on_error) if on_error 63 | o.on_completed(&on_completed) if on_completed 64 | end 65 | end 66 | end 67 | 68 | end 69 | 70 | # Base class for all Observer implementations 71 | class ObserverBase 72 | include Observer 73 | 74 | def initialize(config) 75 | @config = config 76 | @stopped = false 77 | end 78 | 79 | # Unsubscribes from the current observer causing it to transition to the stopped state. 80 | def unsubscribe 81 | @stopped = true 82 | end 83 | 84 | def dispose 85 | unsubscribe 86 | end 87 | 88 | # Notifies the observer of a new element in the sequence. 89 | def on_next(value) 90 | @config.on_next_action.call value unless @stopped 91 | end 92 | 93 | # Notifies the observer that an exception has occurred. 94 | def on_error(error) 95 | raise 'Error cannot be nil' unless error 96 | unless @stopped 97 | @stopped = true 98 | @config.on_error_action.call error 99 | end 100 | end 101 | 102 | # Notifies the observer of the end of the sequence. 103 | def on_completed 104 | unless @stopped 105 | @stopped = true 106 | @config.on_completed_action.call 107 | end 108 | end 109 | 110 | def fail(error) 111 | unless @stopped 112 | @stopped = true 113 | @config.on_error_action.call error 114 | return true 115 | end 116 | return false 117 | end 118 | end 119 | end 120 | -------------------------------------------------------------------------------- /lib/rx/core/scheduled_observer.rb: -------------------------------------------------------------------------------- 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/subscriptions/serial_subscription' 5 | require 'rx/core/observer' 6 | 7 | module Rx 8 | 9 | class ScheduledObserver < ObserverBase 10 | 11 | def initialize(scheduler, observer) 12 | @scheduler = scheduler 13 | @observer = observer 14 | @gate = Monitor.new 15 | @queue = [] 16 | @subscriber = SerialSubscription.new 17 | @acquired = false 18 | @faulted = false 19 | 20 | config = ObserverConfiguration.new 21 | config.on_next(&method(:on_next_core)) 22 | config.on_error(&method(:on_error_core)) 23 | config.on_completed(&method(:on_completed_core)) 24 | 25 | super(config) 26 | end 27 | 28 | def on_next_core(value) 29 | @gate.synchronize { @queue.push(lambda { @observer.on_next value }) } 30 | end 31 | 32 | def on_error_core(error) 33 | @gate.synchronize { @queue.push(lambda { @observer.on_error error }) } 34 | end 35 | 36 | def on_completed_core 37 | @gate.synchronize { @queue.push(lambda { @observer.on_completed }) } 38 | end 39 | 40 | def ensure_active(n=0) 41 | owner = false 42 | 43 | @gate.synchronize do 44 | if !@faulted && @queue.length > 0 45 | owner = !@acquired 46 | @acquired = true 47 | end 48 | end 49 | 50 | @subscriber.subscription = @scheduler.schedule_recursive_with_state(nil, method(:run)) if owner 51 | end 52 | 53 | def run(state, recurse) 54 | work = nil 55 | @gate.synchronize do 56 | if @queue.length > 0 57 | work = @queue.shift 58 | else 59 | @acquired = false 60 | return 61 | end 62 | end 63 | 64 | begin 65 | work.call 66 | rescue => e 67 | @queue = [] 68 | @faulted = true 69 | 70 | raise e 71 | end 72 | 73 | recurse.call state 74 | end 75 | 76 | def unsubscribe 77 | super 78 | @subscriber.unsubscribe 79 | end 80 | 81 | end 82 | 83 | end 84 | -------------------------------------------------------------------------------- /lib/rx/core/synchronized_observer.rb: -------------------------------------------------------------------------------- 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/core/observer' 5 | 6 | module Rx 7 | 8 | module Observer 9 | 10 | class << self 11 | # Synchronizes access to the observer such that its callback methods cannot be called concurrently by multiple threads, using the specified gate object for use by a Monitor based lock. 12 | # This overload is useful when coordinating multiple observers that access shared state by synchronizing on a common gate object if given. 13 | # Notice reentrant observer callbacks on the same thread are still possible. 14 | def allow_reentrancy(observer, gate = Monitor.new) 15 | SynchronizedObserver.new(observer, gate) 16 | end 17 | end 18 | end 19 | 20 | class SynchronizedObserver < Rx::ObserverBase 21 | 22 | def on_next_core(value) 23 | @gate.synchronize { @observer.on_next value } 24 | end 25 | 26 | def on_error_core(error) 27 | @gate.synchronize { @observer.on_error error } 28 | end 29 | 30 | def on_completed_core 31 | @gate.synchronize { @observer.on_completed } 32 | end 33 | 34 | def initialize(observer, gate) 35 | @observer = observer 36 | @gate = gate 37 | 38 | config = ObserverConfiguration.new 39 | config.on_next(&method(:on_next_core)) 40 | config.on_error(&method(:on_error_core)) 41 | config.on_completed(&method(:on_completed_core)) 42 | 43 | super(config) 44 | end 45 | 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/rx/core/time_interval.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | module Rx 4 | 5 | # Record of a value including the virtual time it was produced on. 6 | class TimeInterval < Struct.new(:interval, :value) 7 | 8 | def initialize(interval, value) 9 | super 10 | end 11 | 12 | def to_s 13 | "(#{value})@(#{interval})" 14 | end 15 | 16 | end 17 | end -------------------------------------------------------------------------------- /lib/rx/internal/priority_queue.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | module Rx 4 | 5 | # Priority Queue implemented as a binary heap. 6 | class PriorityQueue 7 | def initialize 8 | @items = [] 9 | @mutex = Mutex.new 10 | end 11 | 12 | def peek 13 | @mutex.synchronize do 14 | unsafe_peek 15 | end 16 | end 17 | 18 | def shift 19 | @mutex.synchronize do 20 | result = unsafe_peek 21 | delete_at 0 22 | result 23 | end 24 | end 25 | 26 | def push(item) 27 | @mutex.synchronize do 28 | @items.push IndexedItem.new(item) 29 | percolate length - 1 30 | end 31 | end 32 | 33 | def delete(item) 34 | @mutex.synchronize do 35 | index = @items.index {|it| it.value == item } 36 | if index 37 | delete_at index 38 | true 39 | else 40 | false 41 | end 42 | end 43 | end 44 | 45 | def length 46 | @items.length 47 | end 48 | 49 | private 50 | 51 | def unsafe_peek 52 | @items.first.value unless @items.empty? 53 | end 54 | 55 | def delete_at(index) 56 | substitute = @items.pop 57 | if substitute and index < @items.length 58 | @items[index] = substitute 59 | heapify index 60 | end 61 | end 62 | 63 | # bubble up an item while it's smaller than parents 64 | def percolate(index) 65 | parent = (index - 1) / 2 66 | return if parent < 0 67 | 68 | current_value = @items[index] 69 | parent_value = @items[parent] 70 | 71 | if current_value < parent_value 72 | @items[index] = parent_value 73 | @items[parent] = current_value 74 | percolate parent 75 | end 76 | end 77 | 78 | # bubble down an item while it's bigger than children 79 | def heapify(index) 80 | current_index = index 81 | left_index = 2 * index + 1 82 | right_index = 2 * index + 2 83 | 84 | current_value = @items[index] 85 | left_value = @items[left_index] 86 | right_value = @items[right_index] 87 | 88 | if right_value && right_value < current_value && right_value < left_value 89 | current_index = right_index 90 | elsif left_value && left_value < current_value 91 | current_index = left_index 92 | end 93 | 94 | if current_index != index 95 | @items[index] = @items[current_index] 96 | @items[current_index] = current_value 97 | heapify current_index 98 | end 99 | end 100 | 101 | class IndexedItem 102 | include Comparable 103 | attr_reader :id , :value 104 | 105 | @@length = 0 106 | 107 | def initialize(value) 108 | @id = @@length += 1 109 | @value = value 110 | end 111 | 112 | def <=>(other) 113 | if @value == other.value 114 | @id <=> other.id 115 | else 116 | @value <=> other.value 117 | end 118 | end 119 | end 120 | 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /lib/rx/internal/util.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def add_ref(r) 4 | AnonymousObservable.new do |observer| 5 | CompositeSubscription.new [r.subscription, self.subscribe(observer)] 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/rx/joins/active_plan.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class ActivePlan 3 | def initialize(join_observer_array, on_next, on_completed) 4 | @join_observer_array = join_observer_array 5 | @on_next = on_next 6 | @on_completed = on_completed 7 | @join_observers = {} 8 | @join_observer_array.each {|x| 9 | @join_observers[x] = x 10 | } 11 | end 12 | 13 | def dequeue 14 | @join_observers.each {|_, v| v.queue.shift } 15 | end 16 | 17 | def match 18 | has_values = true 19 | @join_observer_array.each {|v| 20 | if v.queue.length == 0 21 | has_values = false 22 | break 23 | end 24 | } 25 | if has_values 26 | first_values = [] 27 | is_completed = false 28 | @join_observer_array.each {|v| 29 | first_values.push v.queue[0] 30 | is_completed = true if v.queue[0].on_completed? 31 | } 32 | if is_completed 33 | @on_completed.call 34 | else 35 | dequeue 36 | values = [] 37 | first_values.each {|v| 38 | values.push v.value 39 | } 40 | @on_next.call(*values) 41 | end 42 | end 43 | end 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /lib/rx/joins/join_observer.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class JoinObserver < ObserverBase 3 | 4 | attr_reader :queue 5 | def initialize(source, on_error) 6 | super Observer.configure {|o| 7 | o.on_next {|notification| 8 | if !@is_disposed 9 | if notification.on_error? 10 | @on_error.call(notification.exception) 11 | next 12 | end 13 | @queue.push notification 14 | @active_plans.dup.each {|v| 15 | v.match 16 | } 17 | end 18 | } 19 | } 20 | @source = source 21 | @on_error = on_error 22 | @queue = [] 23 | @active_plans = [] 24 | @subscription = SingleAssignmentSubscription.new 25 | @is_disposed = false 26 | end 27 | 28 | def add_active_plan(active_plan) 29 | @active_plans.push active_plan 30 | end 31 | 32 | def subscribe 33 | @subscription.subscription = @source.materialize.subscribe(@config) 34 | end 35 | 36 | def remove_active_plan(active_plan) 37 | if idx = @active_plans.index(active_plan) 38 | @active_plans.delete_at idx 39 | end 40 | self.unsubscribe if @active_plans.length == 0 41 | end 42 | 43 | def unsubscribe 44 | super 45 | if !@is_disposed 46 | @is_disposed = true 47 | @subscription.unsubscribe 48 | end 49 | end 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/rx/joins/pattern.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class Pattern 3 | attr_reader :patterns 4 | def initialize(patterns) 5 | @patterns = patterns 6 | end 7 | def and(other) 8 | Pattern.new(@patterns.concat(other)) 9 | end 10 | def then_do(selector = Proc.new) 11 | Plan.new(self, selector) 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/rx/joins/plan.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class Plan 3 | def initialize(expression, selector) 4 | @expression = expression 5 | @selector = selector 6 | end 7 | 8 | def activate(external_subscriptions, observer, deactivate) 9 | join_observers = [] 10 | @expression.patterns.each {|pat| 11 | join_observers.push plan_create_observer(external_subscriptions, pat, observer.method(:on_error)) 12 | } 13 | 14 | active_plan = ActivePlan.new(join_observers, lambda {|*args| 15 | begin 16 | result = @selector.call(*args) 17 | rescue => e 18 | observer.on_error e 19 | end 20 | observer.on_next result 21 | }, 22 | lambda { 23 | join_observers.each {|v| 24 | v.remove_active_plan(active_plan) 25 | } 26 | deactivate.call(active_plan) 27 | }) 28 | join_observers.each {|v| 29 | v.add_active_plan(active_plan) 30 | } 31 | return active_plan 32 | end 33 | 34 | def plan_create_observer(external_subscriptions, observable, on_error) 35 | entry = external_subscriptions[observable] 36 | if !entry 37 | observer = JoinObserver.new(observable, on_error) 38 | external_subscriptions[observable] = observer 39 | return observer 40 | end 41 | entry 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/rx/linq/connectable_observable.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class ConnectableObservable < AnonymousObservable 3 | def initialize(source, subject) 4 | @has_subscription = false 5 | @subscription = nil 6 | @source_observable = source.as_observable 7 | @subject = subject 8 | 9 | super(&subject.method(:subscribe)) 10 | end 11 | 12 | def connect 13 | unless @has_subscription 14 | @has_subscription = true 15 | @subscription = CompositeSubscription.new [@source_observable.subscribe(@subject), Subscription.create { @has_subscription = false }] 16 | end 17 | @subscription 18 | end 19 | 20 | def ref_count 21 | count = 0 22 | AnonymousObservable.new do |observer| 23 | count += 1 24 | should_connect = true if count == 1 25 | connectable_subscription = self.connect if should_connect 26 | Subscription.create { 27 | @subscription.unsubscribe 28 | count -= 1 29 | connectable_subscription.unsubscribe if count == 0 30 | } 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/_observable_timer_date_and_period.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | private 4 | def observable_timer_date_and_period(due_time, period, scheduler) 5 | AnonymousObservable.new do |observer| 6 | count = 0 7 | d = due_time 8 | p = Scheduler.normalize(period) 9 | scheduler.schedule_recursive_absolute(d, lambda {|this| 10 | if p > 0 11 | now = scheduler.now() 12 | d = d + p 13 | d <= now && (d = now + p) 14 | end 15 | observer.on_next(count) 16 | count += 1 17 | this.call(d) 18 | }) 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/_observable_timer_time_span.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | private 4 | def observable_timer_time_span(due_time, scheduler) 5 | AnonymousObservable.new do |observer| 6 | scheduler.schedule_relative(Scheduler.normalize(due_time), 7 | lambda { 8 | observer.on_next(0) 9 | observer.on_completed 10 | }) 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/_observable_timer_time_span_and_period.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | private 4 | def observable_timer_time_span_and_period(due_time, period, scheduler) 5 | if due_time == period 6 | AnonymousObservable.new do |observer| 7 | scheduler.schedule_periodic_with_state(0, period, 8 | lambda {|count| 9 | observer.on_next(count) 10 | count + 1 11 | }) 12 | end 13 | else 14 | Observable.defer { 15 | observable_timer_date_and_period(scheduler.now() + due_time, period, scheduler) 16 | } 17 | end 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/aggregate.rb: -------------------------------------------------------------------------------- 1 | require 'rx/operators/aggregates.rb' 2 | 3 | module Rx 4 | module Observable 5 | alias :aggregate :reduce 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/and.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def and(right) 4 | Pattern.new([self, right]); 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/case.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | def case(selector, sources, defaultSourceOrScheduler = Observable.empty) 4 | defer { 5 | if Scheduler === defaultSourceOrScheduler 6 | defaultSourceOrScheduler = Observable.empty(defaultSourceOrScheduler) 7 | end 8 | 9 | result = sources[selector.call] 10 | result || defaultSourceOrScheduler 11 | } 12 | end 13 | alias :switchCase :case 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/concat_all.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def concat_all 4 | merge_concurrent(1) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/concat_map.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def concat_map(selector, result_selector = nil) 4 | if Proc === result_selector 5 | return concat_map(lambda {|x, i| 6 | selector_result = selector.call(x, i) 7 | if selector_result.respond_to?(:each) 8 | selector_result = Observable.from(selector_result) 9 | end 10 | selector_result.map_with_index {|y, i2| 11 | result_selector.call(x, y, i, i2) 12 | } 13 | }) 14 | end 15 | 16 | if Proc === selector 17 | _concat_map(selector) 18 | else 19 | _concat_map(lambda {|*_| selector }) 20 | end 21 | end 22 | 23 | private 24 | 25 | def _concat_map(selector) 26 | map_with_index {|x, i| 27 | result = selector.call(x, i) 28 | if result.respond_to?(:each) 29 | result = Observable.from(result) 30 | end 31 | result 32 | }.concat_all 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/concat_map_observer.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def concat_map_observer(on_next, on_error, on_completed) 4 | AnonymousObservable.new do |observer| 5 | index = 0 6 | 7 | subscribe( 8 | lambda {|x| 9 | begin 10 | result = on_next.call(x, index) 11 | index += 1 12 | rescue => e 13 | observer.on_error e 14 | return 15 | end 16 | observer.on_next result 17 | }, 18 | lambda {|err| 19 | begin 20 | result = on_error.call(err) 21 | rescue => e 22 | observer.on_error e 23 | return 24 | end 25 | 26 | observer.on_next result 27 | observer.on_completed 28 | }, 29 | lambda { 30 | begin 31 | result = on_completed.call 32 | rescue => e 33 | observer.on_error e 34 | return 35 | end 36 | 37 | observer.on_next result 38 | observer.on_completed 39 | }) 40 | end.concat_all 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/contains.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def contains(search_element, from_index = 0) 4 | AnonymousObservable.new do |observer| 5 | i = 0 6 | n = from_index 7 | if n < 0 8 | observer.on_next false 9 | observer.on_completed 10 | return Subscription.empty 11 | end 12 | 13 | subscribe( 14 | lambda {|x| 15 | if i.tap { i += 1 } >= n && x == search_element 16 | observer.on_next true 17 | observer.on_completed 18 | end 19 | }, 20 | observer.method(:on_error), 21 | lambda { 22 | observer.on_next false 23 | observer.on_completed 24 | }) 25 | end 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/debounce.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def debounce(due_time, scheduler = DefaultScheduler.instance) 4 | AnonymousObservable.new do |observer| 5 | cancelable = SerialSubscription.new 6 | hasvalue = false 7 | value = nil 8 | id = 0 9 | 10 | subscription = subscribe( 11 | lambda {|x| 12 | hasvalue = true 13 | value = x 14 | id += 1 15 | current_id = id 16 | d = SingleAssignmentSubscription.new 17 | cancelable.subscription = d 18 | d.subscription = scheduler.schedule_relative(due_time, lambda { 19 | observer.on_next value if hasvalue && id == current_id 20 | hasvalue = false 21 | }) 22 | }, 23 | lambda {|e| 24 | cancelable.dispose 25 | observer.on_error e 26 | hasvalue = false 27 | id += 1 28 | }, 29 | lambda { 30 | cancelable.dispose 31 | observer.on_next value if hasvalue 32 | observer.on_completed 33 | hasvalue = false 34 | id += 1 35 | }) 36 | 37 | CompositeSubscription.new [subscription, cancelable] 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/delay.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def delay(due_time, scheduler = DefaultScheduler.instance) 4 | if Time === due_time 5 | delay_date(due_time, scheduler) 6 | else 7 | delay_time_span(due_time, scheduler) 8 | end 9 | end 10 | 11 | private 12 | 13 | def delay_time_span(due_time, scheduler) 14 | AnonymousObservable.new do |observer| 15 | active = false 16 | cancelable = SerialSubscription.new 17 | exception = nil 18 | q = [] 19 | running = false 20 | subscription = materialize.timestamp(scheduler).subscribe do |notification| 21 | if notification[:value].on_error? 22 | q = [] 23 | q.push notification 24 | exception = notification[:value].error 25 | should_run = !running 26 | else 27 | q.push({ value: notification[:value], timestamp: notification[:timestamp] + due_time }) 28 | should_run = !active 29 | active = true 30 | end 31 | 32 | if should_run 33 | if exception != nil 34 | observer.on_error exception 35 | else 36 | d = SingleAssignmentSubscription.new 37 | cancelable.subscription = d 38 | 39 | d.subscription = scheduler.schedule_recursive_relative(due_time, lambda {|this| 40 | return if exception != nil 41 | 42 | running = true 43 | begin 44 | result = nil 45 | if q.length > 0 && q[0][:timestamp] - scheduler.now <= 0 46 | result = q.shift[:value] 47 | end 48 | if result != nil 49 | result.accept observer 50 | end 51 | end while result != nil 52 | 53 | should_recurse = false 54 | recurse_due_time = 0 55 | if q.length > 0 56 | should_recurse = true 57 | recurse_due_time = [0, q[0][:timestamp] - scheduler.now].max 58 | else 59 | active = false 60 | end 61 | e = exception 62 | running = false 63 | if e != nil 64 | observer.on_error e 65 | elsif should_recurse 66 | this.call recurse_due_time 67 | end 68 | }) 69 | end 70 | end 71 | end 72 | 73 | CompositeSubscription.new [subscription, cancelable] 74 | end 75 | end 76 | 77 | def delay_date(due_time, scheduler) 78 | delay_time_span(due_time - scheduler.now, scheduler) 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/delay_with_selector.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def delay_with_selector(subscription_delay, delay_duration_selector = nil) 4 | if Proc === subscription_delay 5 | selector = subscription_delay 6 | else 7 | sub_delay = subscription_delay 8 | selector = delay_duration_selector 9 | end 10 | 11 | AnonymousObservable.new do |observer| 12 | delays = CompositeSubscription.new 13 | at_end = false 14 | done = lambda { 15 | if at_end && delays.length == 0 16 | observer.on_completed 17 | end 18 | } 19 | subscription = SerialSubscription.new 20 | start = lambda {|*_| 21 | subscription.subscription = subscribe( 22 | lambda {|x| 23 | begin 24 | delay = selector.call(x) 25 | rescue => error 26 | observer.on_error error 27 | return 28 | end 29 | d = SingleAssignmentSubscription.new 30 | delays.push(d) 31 | d.subscription = delay.subscribe( 32 | lambda {|_| 33 | observer.on_next x 34 | delays.delete(d) 35 | done.call 36 | }, 37 | observer.method(:on_error), 38 | lambda { 39 | observer.on_next x 40 | delays.delete(d) 41 | done.call 42 | }) 43 | }, 44 | observer.method(:on_error), 45 | lambda { 46 | at_end = true 47 | subscription.dispose 48 | done.call 49 | }) 50 | } 51 | 52 | if !sub_delay 53 | start.call 54 | else 55 | subscription.subscription = sub_delay.subscribe( 56 | start, 57 | observer.method(:on_error), 58 | start) 59 | end 60 | CompositeSubscription.new [subscription, delays] 61 | end 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/do.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def do(observer_or_on_next = nil, on_error_func = nil, on_completed_func = nil) 4 | if block_given? 5 | on_next_func = Proc.new 6 | elsif Proc === observer_or_on_next 7 | on_next_func = observer_or_on_next 8 | else 9 | on_next_func = observer_or_on_next.method(:on_next) 10 | on_error_func = observer_or_on_next.method(:on_error) 11 | on_completed_func = observer_or_on_next.method(:on_completed) 12 | end 13 | AnonymousObservable.new do |observer| 14 | subscribe( 15 | lambda {|x| 16 | begin 17 | on_next_func.call x 18 | rescue => e 19 | observer.on_error e 20 | end 21 | observer.on_next x 22 | }, 23 | lambda {|err| 24 | begin 25 | on_error_func && on_error_func.call(x) 26 | rescue => e 27 | observer.on_error e 28 | end 29 | observer.on_error err 30 | }, 31 | lambda { 32 | begin 33 | on_completed_func && on_completed_func.call 34 | rescue => e 35 | observer.on_error e 36 | end 37 | observer.on_completed 38 | }) 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/for.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | def for(sources, result_selector = nil) 4 | result_selector ||= lambda {|*args| args} 5 | enum = Enumerator.new {|y| 6 | sources.each {|v| 7 | y << result_selector.call(v) 8 | } 9 | } 10 | Observable.concat(enum) 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/fork_join.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | def fork_join(*all_sources) 4 | AnonymousObservable.new {|subscriber| 5 | count = all_sources.length 6 | if count == 0 7 | subscriber.on_completed 8 | Subscription.empty 9 | end 10 | group = CompositeSubscription.new 11 | finished = false 12 | has_results = Array.new(count) 13 | has_completed = Array.new(count) 14 | results = Array.new(count) 15 | 16 | count.times {|i| 17 | source = all_sources[i] 18 | group.push( 19 | source.subscribe( 20 | lambda {|value| 21 | if !finished 22 | has_results[i] = true 23 | results[i] = value 24 | end 25 | }, 26 | lambda {|e| 27 | finished = true 28 | subscriber.on_error e 29 | group.dispose 30 | }, 31 | lambda { 32 | if !finished 33 | if !has_results[i] 34 | subscriber.on_completed 35 | return 36 | end 37 | has_completed[i] = true 38 | count.times {|ix| 39 | if !has_completed[ix] 40 | return 41 | end 42 | } 43 | finished = true 44 | subscriber.on_next results 45 | subscriber.on_completed 46 | end 47 | } 48 | ) 49 | ) 50 | } 51 | group 52 | } 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/from.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | def from(iterable, map_fn = nil, scheduler = CurrentThreadScheduler.instance) 4 | it = iterable.to_enum 5 | AnonymousObservable.new {|observer| 6 | i = 0 7 | scheduler.schedule_recursive lambda {|this| 8 | begin 9 | result = it.next 10 | rescue StopIteration => e 11 | observer.on_completed 12 | return 13 | rescue => e 14 | observer.on_error e 15 | return 16 | end 17 | 18 | if Proc === map_fn 19 | begin 20 | result = map_fn.call(result, i) 21 | rescue => e 22 | observer.on_error e 23 | return 24 | end 25 | end 26 | 27 | observer.on_next result 28 | i += 1 29 | this.call 30 | } 31 | } 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/group_join.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def group_join(right, left_duration_selector, right_duration_selector, result_selector) 4 | AnonymousObservable.new do |observer| 5 | group = CompositeSubscription.new 6 | r = RefCountSubscription.new(group) 7 | left_map = {} 8 | right_map = {} 9 | left_id = 0 10 | right_id = 0 11 | 12 | left_obs = Observer.configure do |o| 13 | o.on_next {|value| 14 | s = Subject.new 15 | id = left_id 16 | left_id += 1 17 | left_map[id] = s 18 | 19 | begin 20 | result = result_selector.call(value, s.add_ref(r)) 21 | rescue => err 22 | left_map.values.each {|v| v.on_error(err) } 23 | observer.on_error(err) 24 | next 25 | end 26 | observer.on_next(result) 27 | 28 | right_map.values.each {|v| s.on_next(v) } 29 | 30 | md = SingleAssignmentSubscription.new 31 | group.push md 32 | 33 | expire = lambda { 34 | if left_map.delete(id) 35 | s.on_completed 36 | end 37 | group.delete(md) 38 | } 39 | 40 | begin 41 | duration = left_duration_selector.call(value) 42 | rescue => err 43 | left_map.values.each {|v| v.on_error(err) } 44 | observer.on_error(err) 45 | next 46 | end 47 | 48 | md.subscription = duration.take(1).subscribe( 49 | lambda {|_| }, 50 | lambda {|e| 51 | left_map.values.each {|v| v.on_error(e) } 52 | observer.on_error(e) 53 | }, 54 | expire) 55 | } 56 | 57 | o.on_error {|e| 58 | left_map.values.each {|v| v.on_error(e) } 59 | observer.on_error(e) 60 | } 61 | 62 | o.on_completed(&observer.method(:on_completed)) 63 | end 64 | group.push self.subscribe(left_obs) 65 | 66 | right_obs = Observer.configure do |o| 67 | o.on_next {|value| 68 | id = right_id 69 | right_id += 1 70 | right_map[id] = value 71 | 72 | md = SingleAssignmentSubscription.new 73 | group.push md 74 | 75 | expire = lambda { 76 | right_map.delete(id) 77 | group.delete(md) 78 | } 79 | 80 | begin 81 | duration = right_duration_selector.call(value) 82 | rescue => err 83 | right_map.values.each {|v| v.on_error(err) } 84 | observer.on_error(err) 85 | next 86 | end 87 | 88 | md.subscription = duration.take(1).subscribe( 89 | lambda {|_| }, 90 | lambda {|e| 91 | left_map.values.each {|v| v.on_error(e) } 92 | observer.on_error(e) 93 | }, 94 | expire) 95 | } 96 | 97 | o.on_error {|e| 98 | left_map.values.each {|v| v.on_error(e) } 99 | observer.on_error(e) 100 | } 101 | end 102 | group.push right.subscribe(right_obs) 103 | 104 | r 105 | end 106 | end 107 | end 108 | end 109 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/if.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | def if(condition, then_source, else_source_or_scheduler = nil) 4 | case else_source_or_scheduler 5 | when Scheduler 6 | scheduler = else_source_or_scheduler 7 | else_source = Observable.empty(scheduler) 8 | when Observable 9 | else_source = else_source_or_scheduler 10 | when nil 11 | else_source = Observable.empty 12 | end 13 | 14 | return condition.call ? then_source : else_source 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/interval.rb: -------------------------------------------------------------------------------- 1 | module Rx::Observable 2 | def self.interval(period, scheduler = Rx::DefaultScheduler.instance) 3 | observable_timer_time_span_and_period(period, period, scheduler) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/multicast.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def multicast(subject_or_subject_selector, selector = nil) 4 | if Proc === subject_or_subject_selector 5 | AnonymousObservable.new do |observer| 6 | connectable = self.multicast(subject_or_subject_selector.call) 7 | CompositeSubscription.new [selector.call(connectable).subscribe(observer), self] 8 | end 9 | else 10 | ConnectableObservable.new(self, subject_or_subject_selector) 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/of.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | def of(*args) 4 | scheduler = CurrentThreadScheduler.instance 5 | if args.size > 0 && Scheduler === args[0] 6 | scheduler = args.shift 7 | end 8 | of_array(args, scheduler) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/pairs.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | def pairs(obj, scheduler = CurrentThreadScheduler.instance) 4 | of_enumerable(obj, scheduler) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/pluck.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def pluck(prop) 4 | self.map {|x| x[prop]} 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/publish.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def publish(&selector) 4 | if block_given? 5 | multicast(lambda { Subject.new }, Proc.new) 6 | else 7 | multicast(Subject.new) 8 | end 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/sample.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | # Return the latest item from this observable when another observable 4 | # emits an item. 5 | def sample(intervalOrSampler, scheduler = DefaultScheduler.instance, &recipe) 6 | sampler = if intervalOrSampler.is_a? Numeric 7 | Observable.interval(intervalOrSampler, scheduler) 8 | else 9 | intervalOrSampler 10 | end 11 | 12 | AnonymousObservable.new do |observer| 13 | latest = nil 14 | gate = Mutex.new 15 | sampler_observer = Observer.configure do |o| 16 | o.on_next do |sampler_data| 17 | to_emit = nil 18 | gate.synchronize do 19 | to_emit = latest 20 | latest = nil 21 | end 22 | unless to_emit.nil? 23 | to_emit = recipe.call(to_emit, sampler_data) unless recipe.nil? 24 | observer.on_next to_emit 25 | end 26 | end 27 | o.on_error(&observer.method(:on_error)) 28 | o.on_completed(&observer.method(:on_completed)) 29 | end 30 | 31 | sampler_subscription = sampler.subscribe(sampler_observer) 32 | 33 | self_observer = Rx::Observer.configure do |me| 34 | me.on_next do |value| 35 | gate.synchronize { latest = value } 36 | end 37 | me.on_error(&observer.method(:on_error)) 38 | me.on_completed(&observer.method(:on_completed)) 39 | end 40 | 41 | self_subscription = subscribe self_observer 42 | 43 | CompositeSubscription.new [sampler_subscription, self_subscription] 44 | end 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/start.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | def start(func, context, scheduler = DefaultScheduler.instance) 4 | Observable.to_async(func, context, scheduler).call 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/time_interval.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def time_interval(scheduler = DefaultScheduler.instance) 4 | Observable.defer { 5 | last = scheduler.now 6 | self.map {|x| 7 | now = scheduler.now 8 | span = now - last 9 | last = now 10 | TimeInterval.new(span, x) 11 | } 12 | } 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/timer.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | def timer(due_time, period_or_scheduler = DefaultScheduler.instance, scheduler = DefaultScheduler.instance) 4 | case period_or_scheduler 5 | when Numeric 6 | period = period_or_scheduler 7 | when Scheduler 8 | scheduler = period_or_scheduler 9 | end 10 | 11 | if Time === due_time 12 | if period.nil? 13 | observable_timer_date(due_time, scheduler) 14 | else 15 | observable_timer_date_and_period(due_time, period, scheduler) 16 | end 17 | else 18 | if period.nil? 19 | observable_timer_time_span(due_time, scheduler) 20 | else 21 | observable_timer_time_span_and_period(due_time, period, scheduler) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/timestamp.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | module Observable 3 | def timestamp(scheduler = DefaultScheduler.instance) 4 | map do |x| 5 | { value: x, timestamp: scheduler.now } 6 | end 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/to_async.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | def to_async(func, context = nil, scheduler = DefaultScheduler.instance) 4 | lambda() {|*args| 5 | subject = AsyncSubject.new 6 | 7 | scheduler.schedule lambda { 8 | begin 9 | if context 10 | result = proc_bind(func, context).call(*args) 11 | else 12 | result = func.call(*args) 13 | end 14 | rescue => e 15 | subject.on_error e 16 | return 17 | end 18 | subject.on_next result 19 | subject.on_completed 20 | } 21 | return subject.as_observable 22 | } 23 | end 24 | 25 | private 26 | 27 | # derived from Proc#to_method from Ruby Facets 28 | # https://github.com/rubyworks/facets/blob/master/lib/core/facets/proc/to_method.rb 29 | def proc_bind(block, object) 30 | time = Time.now 31 | method_name = "__bind_#{time.to_i}_#{time.usec}" 32 | (class << object; self; end).class_eval do 33 | define_method(method_name, &block) 34 | method = instance_method(method_name) 35 | remove_method(method_name) 36 | method 37 | end.bind(object) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/when.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | def when(*plans) 4 | AnonymousObservable.new do |observer| 5 | active_plans = [] 6 | external_subscriptions = {} 7 | out_observer = Observer.configure {|o| 8 | o.on_next(&observer.method(:on_next)) 9 | o.on_error {|err| 10 | external_subscriptions.each {|_, v| 11 | v.on_error err 12 | } 13 | } 14 | o.on_completed(&observer.method(:on_completed)) 15 | } 16 | begin 17 | plans.each {|x| 18 | active_plans.push x.activate(external_subscriptions, out_observer, lambda {|active_plan| 19 | active_plans.delete(active_plan) 20 | active_plans.length == 0 && observer.on_completed 21 | }) 22 | } 23 | rescue => e 24 | Observable.raise_error(e).subscribe(observer) 25 | end 26 | group = CompositeSubscription.new 27 | external_subscriptions.each {|_, join_observer| 28 | join_observer.subscribe 29 | group.push join_observer 30 | } 31 | 32 | group 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/rx/linq/observable/while.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | class << Observable 3 | def while(condition, source) 4 | enum = Enumerator.new {|y| 5 | while condition.call 6 | y << source 7 | end 8 | } 9 | scheduler = ImmediateScheduler.instance 10 | 11 | is_disposed = false 12 | subscription = SerialSubscription.new 13 | 14 | AnonymousObservable.new do |observer| 15 | cancelable = scheduler.schedule_recursive lambda {|this| 16 | return if is_disposed 17 | 18 | begin 19 | current_value = enum.next 20 | rescue StopIteration => e 21 | observer.on_completed 22 | return 23 | rescue => e 24 | observer.on_error e 25 | return 26 | end 27 | 28 | d = SingleAssignmentSubscription.new 29 | subscription.subscription = d 30 | d.subscription = current_value.subscribe( 31 | observer.method(:on_next), 32 | observer.method(:on_error), 33 | lambda { this.call } 34 | ) 35 | } 36 | 37 | CompositeSubscription.new [subscription, cancelable, Subscription.create { is_disposed = true }] 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/rx/operators/synchronization.rb: -------------------------------------------------------------------------------- 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/subscriptions/single_assignment_subscription' 5 | require 'rx/subscriptions/serial_subscription' 6 | require 'rx/subscriptions/scheduled_subscription' 7 | require 'rx/core/observer' 8 | require 'rx/core/observable' 9 | require 'rx/core/observe_on_observer' 10 | 11 | module Rx 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 48 | -------------------------------------------------------------------------------- /lib/rx/operators/time.rb: -------------------------------------------------------------------------------- 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/concurrency/default_scheduler' 5 | require 'rx/subscriptions/subscription' 6 | require 'rx/subscriptions/composite_subscription' 7 | require 'rx/subscriptions/ref_count_subscription' 8 | require 'rx/subscriptions/serial_subscription' 9 | require 'rx/subscriptions/single_assignment_subscription' 10 | require 'rx/core/observer' 11 | require 'rx/core/observable' 12 | require 'rx/subjects/subject' 13 | 14 | 15 | module Rx 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_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 121 | -------------------------------------------------------------------------------- /lib/rx/subjects/async_subject.rb: -------------------------------------------------------------------------------- 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/core/observer' 5 | require 'rx/core/observable' 6 | require 'rx/subscriptions/subscription' 7 | 8 | module Rx 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 162 | -------------------------------------------------------------------------------- /lib/rx/subjects/behavior_subject.rb: -------------------------------------------------------------------------------- 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/core/observer' 5 | require 'rx/core/observable' 6 | 7 | module Rx 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 -------------------------------------------------------------------------------- /lib/rx/subjects/replay_subject.rb: -------------------------------------------------------------------------------- 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/concurrency/current_thread_scheduler' 5 | require 'rx/core/observer' 6 | require 'rx/core/observable' 7 | require 'rx/core/time_interval' 8 | 9 | module Rx 10 | 11 | # Represents an object that is both an observable sequence as well as an observer. 12 | # Each notification is broadcasted to all subscribed and future observers, subject to buffer trimming policies. 13 | class ReplaySubject 14 | 15 | include Observer 16 | include Observable 17 | 18 | INFINITE_BUFFER_SIZE = Float::MAX.to_i 19 | 20 | def initialize(buffer_size = INFINITE_BUFFER_SIZE, window_size = INFINITE_BUFFER_SIZE, scheduler = CurrentThreadScheduler.instance) 21 | @buffer_size = buffer_size 22 | @window_size = window_size 23 | @scheduler = scheduler 24 | @queue = [] 25 | @observers = [] 26 | @stopped = false 27 | @error = nil 28 | end 29 | 30 | # Indicates whether the subject has observers subscribed to it. 31 | # @return [B] 32 | def has_observers? 33 | observers = @observers 34 | observers && observers.length > 0 35 | end 36 | 37 | end 38 | 39 | end -------------------------------------------------------------------------------- /lib/rx/subjects/subject.rb: -------------------------------------------------------------------------------- 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/core/observer' 5 | require 'rx/core/observable' 6 | require 'rx/subscriptions/subscription' 7 | 8 | module Rx 9 | 10 | # Represents an object that is both an observable sequence as well as an observer. 11 | # Each notification is broadcasted to all subscribed observers. 12 | class Subject 13 | 14 | include Observable 15 | include Observer 16 | 17 | def initialize 18 | @observers = [] 19 | @gate = Mutex.new 20 | @disposed = false 21 | @stopped = false 22 | @error = nil 23 | end 24 | 25 | # Indicates whether the subject has observers subscribed to it. 26 | def has_observers? 27 | @observers && @observers.length > 0 28 | end 29 | 30 | # Notifies all subscribed observers about the end of the sequence. 31 | def on_completed 32 | os = nil 33 | @gate.synchronize do 34 | check_disposed 35 | 36 | unless @stopped 37 | os = @observers.clone 38 | @observers = [] 39 | @stopped = true 40 | end 41 | end 42 | 43 | os.each {|o| o.on_completed } if os 44 | end 45 | 46 | # Notifies all subscribed observers with the error. 47 | def on_error(error) 48 | raise 'error cannot be nil' unless error 49 | 50 | os = nil 51 | @gate.synchronize do 52 | check_disposed 53 | 54 | unless @stopped 55 | os = @observers.clone 56 | @observers = [] 57 | @stopped = true 58 | @error = error 59 | end 60 | end 61 | 62 | os.each {|o| o.on_error error } if os 63 | end 64 | 65 | # Notifies all subscribed observers with the value. 66 | def on_next(value) 67 | os = nil 68 | @gate.synchronize do 69 | check_disposed 70 | os = @observers.clone unless @stopped 71 | end 72 | 73 | os.each {|o| o.on_next value } if os 74 | end 75 | 76 | # Subscribes an observer to the subject. 77 | def subscribe(observer) 78 | raise 'observer cannot be nil' unless observer 79 | 80 | @gate.synchronize do 81 | check_disposed 82 | 83 | if !@stopped 84 | @observers.push(observer) 85 | return InnerSubscription.new(self, observer) 86 | elsif @error 87 | observer.on_error @error 88 | return Subscription.empty 89 | else 90 | observer.on_completed 91 | return Subscription.empty 92 | end 93 | end 94 | end 95 | 96 | # Unsubscribe all observers and release resources. 97 | def unsubscribe 98 | @gate.synchronize do 99 | @disposed = true 100 | @observers = nil 101 | end 102 | end 103 | 104 | class InnerSubscription 105 | def initialize(subject, observer) 106 | @subject = subject 107 | @observer = observer 108 | end 109 | 110 | def unsubscribe 111 | if @observer 112 | @subject.send(:unsubscribe_observer, @observer) 113 | @subject = nil 114 | @observer = nil 115 | end 116 | end 117 | end 118 | 119 | private 120 | 121 | def unsubscribe_observer(observer) 122 | @gate.synchronize do 123 | @observers.delete(observer) if @observers 124 | end 125 | end 126 | 127 | def check_disposed 128 | raise ArgumentError.new 'Subject disposed' if @disposed 129 | end 130 | end 131 | end 132 | -------------------------------------------------------------------------------- /lib/rx/subjects/subject_extensions.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'rx/core/observer' 4 | require 'rx/core/observable' 5 | 6 | module Rx 7 | 8 | # Provides a set of static methods for creating subjects. 9 | class Subject 10 | 11 | # Creates a subject from the specified observer and observable. 12 | def self.create(observer, observable) 13 | AnonymousSubject.new(observer, observable) 14 | end 15 | 16 | class AnonymousSubject 17 | include Observable 18 | include Observer 19 | 20 | def initialize(observer, observable) 21 | @observer = observer 22 | @observable = observable 23 | end 24 | 25 | def on_completed 26 | @observer.on_completed 27 | end 28 | 29 | def on_error(error) 30 | raise 'error cannot be nil' unless error 31 | @observer.on_error(error) 32 | end 33 | 34 | def on_next(value) 35 | @observer.on_next(value) 36 | end 37 | 38 | def subscribe(observer) 39 | raise 'observer cannot be nil' unless observer 40 | @observable.subscribe(observer) 41 | end 42 | end 43 | 44 | end 45 | end -------------------------------------------------------------------------------- /lib/rx/subscriptions/composite_subscription.rb: -------------------------------------------------------------------------------- 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 | 5 | module Rx 6 | 7 | # Represents a group of subscription resources that are unsubscribed together. 8 | class CompositeSubscription 9 | 10 | include Enumerable 11 | 12 | attr_reader :length 13 | 14 | def initialize(subscriptions = []) 15 | @subscriptions = subscriptions 16 | @length = subscriptions.length 17 | @unsubscribed = false 18 | @gate = Mutex.new 19 | end 20 | 21 | # Gets a value that indicates whether the object is unsubscribed. 22 | def unsubscribed? 23 | @unsubscribed 24 | end 25 | 26 | def each(&block) 27 | @subscriptions.each(&block) 28 | end 29 | 30 | # Unsubscribes all subscriptions in the group and removes them from the group. 31 | def unsubscribe 32 | currentSubscriptions = nil 33 | 34 | @gate.synchronize do 35 | unless @unsubscribed 36 | @unsubscribed = true 37 | currentSubscriptions = @subscriptions 38 | @subscriptions = [] 39 | @length = 0 40 | end 41 | end 42 | 43 | currentSubscriptions.each {|subscription| subscription.unsubscribe} if currentSubscriptions 44 | end 45 | 46 | # Adds a subscription to the CompositeSubscription or unsubscribes the subscription if the CompositeSubscription is unsubscribed. 47 | def push(subscription) 48 | should_unsubscribe = false 49 | 50 | @gate.synchronize do 51 | should_unsubscribe = @unsubscribed 52 | 53 | unless @unsubscribed 54 | @subscriptions.push(subscription) 55 | @length += 1 56 | end 57 | end 58 | 59 | subscription.unsubscribe if should_unsubscribe 60 | 61 | return self 62 | end 63 | alias_method :<<, :push 64 | 65 | # Removes and unsubscribes all subscriptions from the CompositeSubscription, but does not dispose the CompositeSubscription. 66 | def clear 67 | currentSubscriptions = nil 68 | 69 | @gate.synchronize do 70 | currentSubscriptions = @subscriptions 71 | @subscriptions = [] 72 | @length = 0 73 | end 74 | currentSubscriptions.each {|subscription| subscription.unsubscribe} 75 | end 76 | 77 | # Removes and unsubscribes the first occurrence of a subscription from the CompositeSubscription. 78 | def delete(subscription) 79 | should_unsubscribe = nil 80 | 81 | @gate.synchronize do 82 | should_unsubscribe = @subscriptions.delete(subscription) 83 | @length -= 1 if should_unsubscribe 84 | end 85 | 86 | subscription.unsubscribe if should_unsubscribe 87 | 88 | should_unsubscribe 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /lib/rx/subscriptions/ref_count_subscription.rb: -------------------------------------------------------------------------------- 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/subscriptions/subscription' 5 | 6 | module Rx 7 | 8 | # Represents a subscription resource that only disposes its underlying subscription resource when all dependent subscription objects have been unsubscribed. 9 | class RefCountSubscription 10 | 11 | def initialize(subscription) 12 | raise ArgumentError.new 'Subscription cannot be nil' unless subscription 13 | 14 | @subscription = subscription 15 | @primary_unsubscribed = false 16 | @gate = Mutex.new 17 | @count = 0 18 | end 19 | 20 | # Gets a value that indicates whether the object is disposed. 21 | def unsubscribed? 22 | @subscription.nil? 23 | end 24 | 25 | # Returns a dependent subscription that when disposed decreases the refcount on the underlying subscription. 26 | def subscription 27 | @gate.synchronize do 28 | if @subscription 29 | @count += 1 30 | return InnerSubscription.new self 31 | else 32 | return Subscription.empty 33 | end 34 | end 35 | end 36 | 37 | # Unsubscribes the underlying subscription only when all dependent subscriptions have been unsubscribed. 38 | def unsubscribe 39 | subscription = nil 40 | @gate.synchronize do 41 | if @subscription 42 | unless @primary_unsubscribed 43 | @primary_unsubscribed = true 44 | 45 | if @count == 0 46 | subscription = @subscription 47 | @subscription = nil 48 | end 49 | end 50 | end 51 | end 52 | 53 | subscription.unsubscribe if subscription 54 | end 55 | 56 | def release 57 | subscription = nil 58 | @gate.synchronize do 59 | if @subscription 60 | @count -= 1 61 | 62 | if @primary_unsubscribed && @count == 0 63 | subscription = @subscription 64 | @subscription = nil 65 | end 66 | end 67 | end 68 | 69 | subscription.unsubscribe if subscription 70 | end 71 | 72 | class InnerSubscription 73 | def initialize(parent) 74 | @parent = parent 75 | end 76 | 77 | def unsubscribe 78 | parent = nil 79 | Mutex.new.synchronize do 80 | parent = @parent 81 | @parent = nil 82 | end 83 | parent.release if parent 84 | end 85 | end 86 | 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /lib/rx/subscriptions/scheduled_subscription.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | module Rx 4 | # Represents a disposable resource whose disposal invocation will be scheduled on the specified scheduler 5 | class ScheduledDisposable 6 | 7 | attr_reader :scheduler, :subscription 8 | 9 | def initialize(scheduler, subscription) 10 | raise 'disposable cannot be nil' unless subscription 11 | raise 'scheduler cannot be nil' unless scheduler 12 | 13 | @scheduler = scheduler 14 | @subscription = subscription 15 | end 16 | 17 | # Gets a value that indicates whether the object is unsubscribed. 18 | def unsubscribed? 19 | @subscription.nil? 20 | end 21 | 22 | # Unsubscribes the wrapped subscription on the provided scheduler. 23 | def unsubscribe 24 | @scheduler.schedule lambda do 25 | unless @subscription.nil? 26 | @subscription.unsubscribe 27 | @subscription = nil 28 | end 29 | end 30 | end 31 | end 32 | end -------------------------------------------------------------------------------- /lib/rx/subscriptions/serial_subscription.rb: -------------------------------------------------------------------------------- 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 | 5 | module Rx 6 | 7 | # Represents a subscription resource whose underlying subscription resource can be replaced by another subscription resource, causing automatic disposal of the previous underlying subscription resource. 8 | class SerialSubscription 9 | include Subscription 10 | 11 | def initialize 12 | @gate = Mutex.new 13 | @current = nil 14 | @unsubscribed = false 15 | end 16 | 17 | # Gets a value that indicates whether the object is unsubscribed. 18 | def unsubscribed? 19 | @gate.synchronize do 20 | return @unsubscribed 21 | end 22 | end 23 | 24 | # Gets the underlying subscription. 25 | def subscription 26 | @current 27 | end 28 | 29 | # Sets the underlying subscription. 30 | def subscription=(new_subscription) 31 | should_unsubscribe = false 32 | old = nil 33 | @gate.synchronize do 34 | should_unsubscribe = @unsubscribed 35 | unless should_unsubscribe 36 | old = @current 37 | @current = new_subscription 38 | end 39 | end 40 | 41 | old.unsubscribe if old 42 | new_subscription.unsubscribe if should_unsubscribe && !new_subscription.nil? 43 | end 44 | 45 | # Unsubscribes the current underlying subscription and all future subscriptions. 46 | def unsubscribe 47 | old = nil 48 | @gate.synchronize do 49 | unless @unsubscribed 50 | @unsubscribed = true 51 | old = @current 52 | @current = nil 53 | end 54 | end 55 | 56 | old.unsubscribe if old 57 | end 58 | 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /lib/rx/subscriptions/single_assignment_subscription.rb: -------------------------------------------------------------------------------- 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 | 5 | module Rx 6 | 7 | # Represents a subscription resource which only allows a single assignment of its underlying subscription resource. 8 | # If an underlying subscription resource has already been set, future attempts to set the underlying subscription resource will throw an error 9 | class SingleAssignmentSubscription 10 | 11 | def initialize 12 | @gate = Mutex.new() 13 | @current = nil 14 | @unsubscribed = false 15 | @set = false 16 | end 17 | 18 | # Gets a value that indicates whether the object is unsubscribed. 19 | def unsubscribed? 20 | @gate.synchronize do 21 | return @unsubscribed 22 | end 23 | end 24 | 25 | # Gets the underlying subscription. After unsubscribing, the result of getting this property is undefined. 26 | def subscription 27 | @current 28 | end 29 | 30 | # Sets the underlying disposable. If this has already been set, then an error is raised. 31 | def subscription=(new_subscription) 32 | raise 'Subscription already set' if @set 33 | 34 | @set = true 35 | should_unsubscribe = false 36 | old = nil 37 | @gate.synchronize do 38 | should_unsubscribe = @unsubscribed 39 | unless should_unsubscribe 40 | old = @current 41 | @current = new_subscription 42 | end 43 | end 44 | 45 | old.unsubscribe if old 46 | new_subscription.unsubscribe if should_unsubscribe && !new_subscription.nil? 47 | end 48 | 49 | # Unsubscribes the underlying subscription 50 | def unsubscribe 51 | old = nil 52 | @gate.synchronize do 53 | unless @unsubscribed 54 | @unsubscribed = true 55 | old = @current 56 | @current = nil 57 | end 58 | end 59 | 60 | old.unsubscribe if old 61 | end 62 | 63 | end 64 | end -------------------------------------------------------------------------------- /lib/rx/subscriptions/subscription.rb: -------------------------------------------------------------------------------- 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 'singleton' 5 | 6 | module Rx 7 | module Subscription 8 | def dispose 9 | unsubscribe 10 | end 11 | end 12 | Disposable = Subscription 13 | 14 | class EmptySubscription 15 | 16 | include Subscription 17 | include Singleton 18 | 19 | def unsubscribe 20 | 21 | end 22 | end 23 | 24 | class AnonymousSubscription 25 | include Subscription 26 | 27 | def initialize(&unsubscribe_action) 28 | @unsubscribe_action = unsubscribe_action 29 | @gate = Mutex.new 30 | @unsubscribed = false 31 | end 32 | 33 | def unsubscribe 34 | should_unsubscribe = false 35 | @gate.synchronize do 36 | should_unsubscribe = !@unsubscribed 37 | end 38 | 39 | @unsubscribe_action.call if should_unsubscribe 40 | end 41 | end 42 | 43 | # Provides a set of class methods for creating Disposables. 44 | module Subscription 45 | 46 | # Creates a subscription object that invokes the specified action when unsubscribed. 47 | def self.create(&unsubscribe_action) 48 | AnonymousSubscription.new(&unsubscribe_action) 49 | end 50 | 51 | # Gets the subscription that does nothing when unsubscribed. 52 | def self.empty 53 | EmptySubscription.instance 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/rx/testing/cold_observable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'rx/subscriptions/subscription' 4 | require 'rx/subscriptions/composite_subscription' 5 | require 'rx/testing/test_subscription' 6 | 7 | module Rx 8 | 9 | class ColdObservable 10 | include Observable 11 | 12 | attr_reader :messages, :subscriptions 13 | 14 | def initialize(scheduler, *args) 15 | raise 'scheduler cannot be nil' unless scheduler 16 | 17 | @scheduler = scheduler 18 | @messages = args 19 | @subscriptions = [] 20 | end 21 | 22 | def subscribe(observer) 23 | raise 'observer cannot be nil' unless observer 24 | 25 | subscriptions.push(TestSubscription.new @scheduler.clock) 26 | index = subscriptions.length - 1 27 | 28 | d = CompositeSubscription.new 29 | 30 | messages.each do |message| 31 | notification = message.value 32 | 33 | d.push(@scheduler.schedule_at_relative_with_state(nil, message.time, lambda {|scheduler1, state1| 34 | notification.accept observer 35 | Subscription.empty 36 | })) 37 | end 38 | 39 | return Subscription.create do 40 | subscriptions[index] = TestSubscription.new(subscriptions[index].subscribe, @scheduler.clock) 41 | d.unsubscribe 42 | end 43 | end 44 | 45 | end 46 | end -------------------------------------------------------------------------------- /lib/rx/testing/hot_observable.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'rx/core/observable' 4 | require 'rx/subscriptions/subscription' 5 | require 'rx/testing/test_subscription' 6 | 7 | module Rx 8 | 9 | class HotObservable 10 | include Observable 11 | 12 | attr_reader :messages, :subscriptions 13 | 14 | def initialize(scheduler, *args) 15 | raise 'scheduler cannot be nil' unless scheduler 16 | 17 | @scheduler = scheduler 18 | @messages = args 19 | @subscriptions = [] 20 | @observers = [] 21 | 22 | @messages.each do |message| 23 | notification = message.value 24 | @scheduler.schedule_at_relative_with_state(nil, message.time, lambda {|scheduler1, state1| 25 | 26 | @observers.clone.each {|observer| notification.accept observer } 27 | 28 | Subscription.empty 29 | }) 30 | end 31 | end 32 | 33 | def subscribe(observer) 34 | raise 'observer cannot be nil' unless observer 35 | 36 | @observers.push observer 37 | subscriptions.push (TestSubscription.new @scheduler.clock) 38 | 39 | index = subscriptions.length - 1 40 | 41 | Subscription.create do 42 | @observers.delete observer 43 | subscriptions[index] = TestSubscription.new(subscriptions[index].subscribe, @scheduler.clock) 44 | end 45 | end 46 | end 47 | end -------------------------------------------------------------------------------- /lib/rx/testing/mock_observer.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'rx/core/observer' 4 | require 'rx/core/notification' 5 | require 'rx/testing/recorded' 6 | 7 | module Rx 8 | 9 | class MockObserver 10 | include Observer 11 | 12 | attr_reader :messages 13 | 14 | def initialize(scheduler) 15 | raise 'scheduler cannot be nil' unless scheduler 16 | 17 | @scheduler = scheduler 18 | @messages = [] 19 | end 20 | 21 | def on_next(value) 22 | messages.push(Recorded.new(@scheduler.clock, Notification.create_on_next(value))) 23 | end 24 | 25 | def on_error(error) 26 | messages.push(Recorded.new(@scheduler.clock, Notification.create_on_error(error))) 27 | end 28 | 29 | def on_completed 30 | messages.push(Recorded.new(@scheduler.clock, Notification.create_on_completed)) 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /lib/rx/testing/reactive_test.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'rx/core/notification' 4 | require 'rx/testing/recorded' 5 | 6 | module Rx 7 | 8 | # Module to write unit tests for applications and libraries built using Reactive Extensions. 9 | module ReactiveTest 10 | 11 | # Default virtual time used for creation of observable sequences in ReactiveTest-based unit tests. 12 | CREATED = 100 13 | 14 | # Default virtual time used to subscribe to observable sequences in ReactiveTest-based unit tests. 15 | SUBSCRIBED = 200 16 | 17 | # Default virtual time used to dispose subscriptions in ReactiveTest-based unit tests. 18 | DISPOSED = 1000 19 | 20 | # Factory method for an on_next notification record at a given time with a given value. 21 | def on_next(ticks, value) 22 | Recorded.new(ticks, Notification.create_on_next(value)) 23 | end 24 | 25 | # Factory method for writing an assert that checks for an on_next notification record at a given time, using the specified predicate to check the value. 26 | def on_next_predicate(ticks, &block) 27 | n = OnNextPredicate.new(&block) 28 | Recorded.new(ticks, n) 29 | end 30 | 31 | # Factory method for an on_error notification record at a given time with a given error. 32 | def on_error(ticks, error) 33 | Recorded.new(ticks, Notification.create_on_error(error)) 34 | end 35 | 36 | # Factory method for writing an assert that checks for an on_error notification record at a given time, using the specified predicate to check the exception. 37 | def on_error_predicate(ticks, &block) 38 | n = OnErrorPredicate.new(&block) 39 | Recorded.new(ticks, n) 40 | end 41 | 42 | # Factory method for an OnCompleted notification record at a given time. 43 | def on_completed(ticks) 44 | Recorded.new(ticks, Notification.create_on_completed) 45 | end 46 | 47 | # Factory method for a subscription record based on a given subscription and unsubscribe time. 48 | def subscribe(subscribe, unsubscribe) 49 | TestSubscription.new(subscribe, unsubscribe) 50 | end 51 | 52 | def assert_messages(expected, actual) 53 | assert_equal expected.length, actual.length, "The size of messages differ" 54 | 55 | for i in 0..expected.length - 1 56 | assert_equal expected[i].time, actual[i].time, "The messages[#{i}].time differ" 57 | assert_equal expected[i].value, actual[i].value, "The messages[#{i}].value differ" 58 | end 59 | end 60 | 61 | def assert_subscriptions(expected, actual) 62 | assert_equal expected.length, actual.length 63 | 64 | for i in 0..expected.length - 1 65 | assert (expected[i] == actual[i]) 66 | end 67 | end 68 | 69 | class OnNextPredicate 70 | 71 | def initialize(&action) 72 | @action = action 73 | end 74 | 75 | def ==(other) 76 | other && other.on_next? && @action.call(other.value) 77 | end 78 | alias_method :eql?, :== 79 | end 80 | 81 | class OnErrorPredicate 82 | 83 | def initialize(&action) 84 | @action = action 85 | end 86 | 87 | def ==(other) 88 | other && other.on_error? && @action.call(other.error) 89 | end 90 | alias_method :eql?, :== 91 | end 92 | 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /lib/rx/testing/recorded.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | module Rx 4 | 5 | # Record of a value including the virtual time it was produced on. 6 | class Recorded < Struct.new(:time, :value) 7 | 8 | def initialize(time, value) 9 | super 10 | end 11 | 12 | def to_s 13 | "#{value} @ #{time}" 14 | end 15 | 16 | end 17 | end -------------------------------------------------------------------------------- /lib/rx/testing/test_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'rx/concurrency/virtual_time_scheduler' 4 | require 'rx/subscriptions/subscription' 5 | require 'rx/testing/cold_observable' 6 | require 'rx/testing/hot_observable' 7 | require 'rx/testing/mock_observer' 8 | require 'rx/testing/reactive_test' 9 | 10 | module Rx 11 | 12 | # Virtual time scheduler used for testing applications and libraries built using Reactive Extensions. 13 | class TestScheduler < VirtualTimeScheduler 14 | 15 | def initialize 16 | super(0) 17 | end 18 | 19 | # Schedules an action to be executed at due_time. 20 | def schedule_at_absolute_with_state(state, due_time, action) 21 | raise 'action cannot be nil' unless action 22 | 23 | due_time = clock + 1 if due_time <= clock 24 | 25 | super(state, due_time, action) 26 | end 27 | 28 | # Adds a relative virtual time to an absolute virtual time value. 29 | def add(absolute, relative) 30 | absolute + relative 31 | end 32 | 33 | # Converts the absolute time value to a Time value. 34 | def to_time(absolute) 35 | Time.at absolute 36 | end 37 | 38 | # Converts the time span value to a relative time value. 39 | def to_relative(time_span) 40 | time_span 41 | end 42 | 43 | # Starts the test scheduler and uses the specified virtual times to invoke the factory function, subscribe to the resulting sequence, and unsubscribe the subscription. 44 | def configure(options = {}) 45 | options.each {|key,_| 46 | unless [:created, :subscribed, :disposed].include? key 47 | raise ArgumentError, "Should be specified whether :created, :subscribed or :disposed, but the #{key.inspect}" 48 | end 49 | } 50 | o = { 51 | :created => ReactiveTest::CREATED, 52 | :subscribed => ReactiveTest::SUBSCRIBED, 53 | :disposed => ReactiveTest::DISPOSED 54 | }.merge(options) 55 | 56 | source = nil 57 | subscription = nil 58 | observer = create_observer 59 | 60 | schedule_at_absolute_with_state(nil, o[:created], lambda {|scheduler, state| 61 | source = yield 62 | Subscription.empty 63 | }) 64 | 65 | schedule_at_absolute_with_state(nil, o[:subscribed], lambda {|scheduler, state| 66 | subscription = source.subscribe observer 67 | Subscription.empty 68 | }) 69 | 70 | schedule_at_absolute_with_state(nil, o[:disposed], lambda {|scheduler, state| 71 | subscription.unsubscribe 72 | Subscription.empty 73 | }) 74 | 75 | start 76 | 77 | observer 78 | end 79 | 80 | # Creates a hot observable using the specified timestamped notification messages. 81 | def create_hot_observable(*args) 82 | HotObservable.new(self, *args) 83 | end 84 | 85 | # Creates a cold observable using the specified timestamped notification messages. 86 | def create_cold_observable(*args) 87 | ColdObservable.new(self, *args) 88 | end 89 | 90 | # Creates an observer that records received notification messages and timestamps those. 91 | def create_observer 92 | MockObserver.new self 93 | end 94 | 95 | end 96 | end 97 | -------------------------------------------------------------------------------- /lib/rx/testing/test_subscription.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | module Rx 4 | 5 | # Records information about subscriptions to and unsubscriptions from observable sequences. 6 | class TestSubscription < Struct.new(:subscribe, :unsubscribe) 7 | 8 | FIXNUM_MAX = Float::MAX.to_i 9 | 10 | def initialize(subscribe, unsubscribe = FIXNUM_MAX) 11 | super 12 | end 13 | 14 | def infinite? 15 | unsubscribe == FIXNUM_MAX 16 | end 17 | 18 | def to_s 19 | "#{subscribe}, #{infinite? ? 'Infinite' : unsubscribe}" 20 | end 21 | end 22 | end -------------------------------------------------------------------------------- /lib/rx/version.rb: -------------------------------------------------------------------------------- 1 | module Rx 2 | VERSION = '0.0.3' 3 | end 4 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) Microsoft Open Technologies. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you 4 | may not use this file except in compliance with the License. You may 5 | obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | implied. See the License for the specific language governing permissions 13 | and limitations under the License. -------------------------------------------------------------------------------- /rx.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | lib = File.expand_path('../lib', __FILE__) 3 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 4 | require 'rx/version' 5 | 6 | Gem::Specification.new do |gem| 7 | gem.authors = ["Microsoft Open Technologies, Inc."] 8 | gem.description = %q{Reactive Extensions for Ruby} 9 | gem.summary = %q{This is an implementation of the Reactive Extensions for Ruby. Note that this is an early prototype, but contributions are welcome.} 10 | gem.homepage = "https://github.com/ReactiveX/RxRuby" 11 | 12 | gem.files = `git ls-files`.split($\) 13 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 14 | gem.name = "rx" 15 | gem.require_paths = ["lib"] 16 | gem.version = Rx::VERSION 17 | gem.license = 'Apache License, v2.0' 18 | 19 | gem.add_development_dependency 'rake' 20 | gem.add_development_dependency 'minitest' 21 | gem.add_development_dependency 'simplecov' 22 | end 23 | -------------------------------------------------------------------------------- /test/rx/concurrency/helpers/historical_virtual_scheduler_helper.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | module HistoricalVirtualSchedulerTestHelper 4 | 5 | # Scheduler state 6 | 7 | def test_disabled_by_default 8 | assert_equal(false, @scheduler.enabled?) 9 | end 10 | 11 | def test_disabled_once_out_of_tasks 12 | @scheduler.start 13 | assert_equal(false, @scheduler.enabled?) 14 | end 15 | 16 | def test_enabled_while_running 17 | @scheduler.schedule ->() { assert_equal(true, @scheduler.enabled?) } 18 | @scheduler.start 19 | end 20 | 21 | def test_stop 22 | @scheduler.schedule ->() { @scheduler.stop } 23 | @scheduler.schedule ->() { flunk "Should be stopped" } 24 | @scheduler.start 25 | 26 | assert_equal(false, @scheduler.enabled?) 27 | end 28 | 29 | def test_now 30 | assert_equal(@start, @scheduler.now) 31 | end 32 | 33 | def test_clock 34 | assert_equal @start, @scheduler.clock 35 | end 36 | 37 | # Relative Scheduling 38 | 39 | def test_relative_with_state 40 | state = [] 41 | task = ->(_, s) { s.push 1 } 42 | @scheduler.schedule_at_relative_with_state(state, 2, task) 43 | @scheduler.start 44 | 45 | assert_equal([1], state) 46 | assert_equal(@start + 2, @scheduler.now) 47 | end 48 | 49 | def test_relative 50 | ran = false 51 | task = ->() { ran = true } 52 | @scheduler.schedule_at_relative(2, task) 53 | @scheduler.start 54 | 55 | assert_equal(true, ran) 56 | assert_equal(@start + 2, @scheduler.now) 57 | end 58 | 59 | # Absolute Scheduling 60 | 61 | def test_absolute_with_state 62 | state = [] 63 | time = @start + 2 64 | task = ->(_, s) { s.push 1 } 65 | @scheduler.schedule_at_absolute_with_state(state, time, task) 66 | @scheduler.start 67 | 68 | assert_equal([1], state) 69 | assert_equal(time, @scheduler.now) 70 | end 71 | 72 | def test_absolute 73 | ran = false 74 | time = @start + 2 75 | task = ->() { ran = true } 76 | @scheduler.schedule_at_absolute(time, task) 77 | @scheduler.start 78 | 79 | assert_equal(true, ran) 80 | assert_equal(time, @scheduler.now) 81 | end 82 | 83 | # Time manipulation 84 | 85 | def test_advance 86 | ran = false 87 | task = ->() { ran = true } 88 | failure = ->() { flunk "Should never reach." } 89 | 90 | @scheduler.schedule_at_absolute(@start + 10, task) 91 | @scheduler.schedule_at_absolute(@start + 11, failure) 92 | @scheduler.advance_to(@start + 10) 93 | 94 | assert_equal(true, ran) 95 | assert_equal(@start + 10, @scheduler.now) 96 | end 97 | 98 | def test_advance_raises_if_running 99 | task = ->() do 100 | assert_raises(RuntimeError) { @scheduler.advance_to(@start + 10) } 101 | end 102 | 103 | @scheduler.schedule task 104 | @scheduler.start 105 | end 106 | 107 | def test_advance_by 108 | ran = false 109 | task = ->() { ran = true } 110 | failure = ->() { flunk "Should never reach." } 111 | 112 | @scheduler.schedule_at_relative(10, task) 113 | @scheduler.schedule_at_relative(11, failure) 114 | @scheduler.advance_by(10) 115 | 116 | assert_equal(true, ran) 117 | assert_equal(@start + 10, @scheduler.now) 118 | end 119 | 120 | def test_advance_raises_if_out_of_range 121 | assert_raises(RuntimeError) { @scheduler.advance_by(-10) } 122 | end 123 | 124 | def test_sleep 125 | failure = ->() { flunk "Should not run." } 126 | @scheduler.schedule_at_relative(10, failure) 127 | @scheduler.sleep(20) 128 | 129 | assert_equal(@start + 20, @scheduler.now) 130 | end 131 | 132 | def test_sleep_raises_if_out_of_range 133 | assert_raises(RuntimeError) { @scheduler.sleep(-10) } 134 | end 135 | end 136 | -------------------------------------------------------------------------------- /test/rx/concurrency/helpers/immediate_local_scheduler_helper.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | 5 | module ImmediateLocalSchedulerTestHelper 6 | def test_now 7 | assert_equal(Time.now.to_i, @scheduler.now.to_i) 8 | end 9 | 10 | def test_schedule_with_state 11 | state = [] 12 | task = ->(_, s) { s << 1 } 13 | @scheduler.schedule_with_state(state, task) 14 | 15 | assert_equal([1], state) 16 | end 17 | 18 | def test_schedule_with_state_simple_absolute 19 | state = [] 20 | task = ->(_, s) { s << 1 } 21 | @scheduler.schedule_absolute_with_state(state, Time.now, task) 22 | 23 | assert_equal([1], state) 24 | end 25 | 26 | def test_schedule_recursive_absolute_with_state_simple 27 | state = [] 28 | inner = ->(_, s) { s << 1 } 29 | outer = ->(s, x) { s.schedule_absolute_with_state(x, Time.now, inner) } 30 | @scheduler.schedule_absolute_with_state(state, Time.now, outer) 31 | 32 | assert_equal([1], state) 33 | end 34 | 35 | def test_schedule_with_state_simple_relative 36 | state = [] 37 | task = ->(_, s) { s << 1 } 38 | @scheduler.schedule_relative_with_state(state, 0, task) 39 | 40 | assert_equal([1], state) 41 | end 42 | 43 | def test_schedule_recursive_relative_with_state_simple 44 | state = [] 45 | inner = ->(_, s) { s << 1 } 46 | outer = ->(sched, s) { sched.schedule_relative_with_state(s, 1, inner) } 47 | @scheduler.schedule_relative_with_state(state, 1, outer) 48 | 49 | assert_equal([1], state) 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/rx/concurrency/test_async_lock.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | 5 | class TestAsyncLock < Minitest::Test 6 | def setup 7 | @lock = Rx::AsyncLock.new 8 | end 9 | 10 | def test_simple_wait 11 | called = false 12 | @lock.wait { called = true } 13 | assert_equal(true, called) 14 | end 15 | 16 | def test_parallel_wait 17 | state = [false, false] 18 | sync = [Queue.new, Queue.new] 19 | 20 | thread1 = Thread.new do 21 | sync[0].pop 22 | @lock.wait do 23 | sync[1].push 1 24 | sync[0].pop 25 | state[0] = true 26 | end 27 | state.each { |s| assert_equal(true, s) } 28 | end 29 | 30 | thread2 = Thread.new do 31 | sync[1].pop 32 | @lock.wait do 33 | assert_equal(thread1, Thread.current) 34 | state[1] = true 35 | end 36 | state.each { |s| assert_equal(false, s) } 37 | sync[0].push 1 38 | end 39 | 40 | sync[0].push 1 41 | [thread1, thread2].each(&:join) 42 | end 43 | 44 | def test_clear 45 | @lock.clear 46 | called = false 47 | @lock.wait { called = true } 48 | assert_equal(false, called) 49 | end 50 | 51 | def test_exceptions_bubble_up_and_fault 52 | assert_raises(StandardError) { @lock.wait { raise StandardError } } 53 | assert_equal(true, @lock.send(:instance_variable_get, :@has_faulted)) 54 | assert_equal([], @lock.send(:instance_variable_get, :@queue)) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/rx/concurrency/test_current_thread_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | require 'rx/concurrency/helpers/immediate_local_scheduler_helper' 5 | 6 | 7 | class TestCurrentThreadScheduler < Minitest::Test 8 | include ImmediateLocalSchedulerTestHelper 9 | 10 | def setup 11 | @scheduler = Rx::CurrentThreadScheduler.instance 12 | end 13 | 14 | def test_schedule_required 15 | assert_equal(true, Rx::CurrentThreadScheduler.schedule_required?) 16 | end 17 | 18 | def test_schedule 19 | ran = false 20 | @scheduler.schedule -> { ran = true } 21 | 22 | assert_equal(true, ran) 23 | end 24 | 25 | def test_schedule_runs_in_current_thead 26 | id = Thread.current.object_id 27 | @scheduler.schedule -> { assert_equal(id, Thread.current.object_id) } 28 | end 29 | 30 | def test_schedule_error_raises 31 | assert_raises(StandardError) do 32 | @scheduler.schedule -> { raise StandardError } 33 | end 34 | end 35 | 36 | def test_schedule_nested 37 | ran = false 38 | @scheduler.schedule -> do 39 | @scheduler.schedule -> { ran = true } 40 | end 41 | 42 | assert_equal(true, ran) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/rx/concurrency/test_default_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | 5 | # DefaultScheduler creates new threads in which to run scheduled tasks; a short 6 | # sleep is necessary to allow the thread scheduler to yield to the other 7 | # threads. 8 | class TestDefaultScheduler < Minitest::Test 9 | 10 | def setup 11 | @scheduler = Rx::DefaultScheduler.instance 12 | end 13 | 14 | def test_schedule_with_state 15 | state = [] 16 | task = ->(_, s) { s << 1 } 17 | @scheduler.schedule_with_state(state, task) 18 | sleep 0.001 19 | 20 | assert_equal([1], state) 21 | end 22 | 23 | def test_schedule_relative_with_state 24 | state = [] 25 | task = ->(_, s) { s << 1 } 26 | @scheduler.schedule_relative_with_state(state, 0.05, task) 27 | sleep 0.1 28 | 29 | assert_equal([1], state) 30 | end 31 | 32 | def test_default_schedule_runs_in_its_own_thread 33 | id = Thread.current.object_id 34 | @scheduler.schedule -> { refute_equal(id, Thread.current.object_id) } 35 | sleep 0.001 36 | end 37 | 38 | def test_schedule_action_cancel 39 | task = -> { flunk "This should not run." } 40 | subscription = @scheduler.schedule_relative(0.05, task) 41 | subscription.unsubscribe 42 | sleep 0.1 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/rx/concurrency/test_historical_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | require 'rx/concurrency/helpers/historical_virtual_scheduler_helper' 5 | 6 | class TestHistoricalScheduler < Minitest::Test 7 | include HistoricalVirtualSchedulerTestHelper 8 | 9 | def setup 10 | @start = Time.at(1000) 11 | @scheduler = Rx::HistoricalScheduler.new(@start) 12 | end 13 | 14 | def test_initialization 15 | assert_equal(Time.at(1000), @scheduler.now) 16 | assert_equal(Time.at(0), Rx::HistoricalScheduler.new.now) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /test/rx/concurrency/test_immediate_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | require 'rx/concurrency/helpers/immediate_local_scheduler_helper' 5 | 6 | class TestImmediateScheduler < Minitest::Test 7 | include ImmediateLocalSchedulerTestHelper 8 | 9 | def setup 10 | @scheduler = Rx::ImmediateScheduler.instance 11 | end 12 | 13 | def test_now 14 | assert_equal(Time.now.to_i, @scheduler.now.to_i) 15 | end 16 | 17 | def test_immediate_schedule 18 | ran = false 19 | @scheduler.schedule -> { ran = true } 20 | assert_equal(true, ran) 21 | end 22 | 23 | def test_immediate_schedule_runs_in_current_thread 24 | id = Thread.current.object_id 25 | @scheduler.schedule -> { assert_equal(id, Thread.current.object_id) } 26 | end 27 | 28 | def test_schedule_error_raises 29 | task = -> do 30 | raise(StandardError) 31 | flunk "Should not be reached." 32 | end 33 | 34 | assert_raises(StandardError) { @scheduler.schedule(task) } 35 | end 36 | 37 | def test_schedule_with_state_simple 38 | state = [] 39 | task = ->(_, s) { s << 1 } 40 | @scheduler.schedule_with_state(state, task) 41 | 42 | assert_equal([1], state) 43 | end 44 | 45 | def test_schedule_recursive_with_state_simple 46 | state = [] 47 | inner = ->(_, s) { s << 1 } 48 | outer = ->(sched, s) { sched.schedule_with_state(s, inner) } 49 | @scheduler.schedule_with_state(state, outer) 50 | 51 | assert_equal([1], state) 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /test/rx/concurrency/test_local_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | require 'rx/concurrency/helpers/immediate_local_scheduler_helper' 5 | 6 | class TestLocalcheduler < Minitest::Test 7 | include ImmediateLocalSchedulerTestHelper 8 | 9 | def setup 10 | @scheduler = Rx::LocalScheduler.new 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/rx/concurrency/test_periodic_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | 5 | class PeriodicTestClass 6 | include Rx::PeriodicScheduler 7 | end 8 | 9 | def await_array_length(array, expected, interval) 10 | sleep (expected * interval) * 0.9 11 | deadline = Time.now + interval * (expected + 1) 12 | while Time.now < deadline 13 | break if array.length == expected 14 | sleep interval / 10 15 | end 16 | end 17 | 18 | class TestPeriodicScheduler < Minitest::Test 19 | def setup 20 | @scheduler = PeriodicTestClass.new 21 | end 22 | 23 | INTERVAL = 0.05 24 | 25 | def test_periodic_with_state 26 | state = [] 27 | task = ->(x) { x << 1 } 28 | 29 | subscription = @scheduler.schedule_periodic_with_state(state, INTERVAL, task) 30 | await_array_length(state, 2, INTERVAL) 31 | subscription.unsubscribe 32 | assert_equal(state.length, 2) 33 | end 34 | 35 | def test_periodic_with_state_exceptions 36 | assert_raises(RuntimeError) do 37 | @scheduler.schedule_periodic_with_state([], INTERVAL, nil) 38 | end 39 | 40 | assert_raises(RuntimeError) do 41 | @scheduler.schedule_periodic_with_state([], -1, ->{}) 42 | end 43 | end 44 | 45 | def test_periodic 46 | state = [] 47 | task = ->() { state << 1 } 48 | 49 | subscription = @scheduler.schedule_periodic(INTERVAL, task) 50 | await_array_length(state, 2, INTERVAL) 51 | subscription.unsubscribe 52 | assert_equal(state.length, 2) 53 | end 54 | 55 | def test_periodic_exceptions 56 | assert_raises(RuntimeError) do 57 | @scheduler.schedule_periodic(INTERVAL, nil) 58 | end 59 | 60 | assert_raises(RuntimeError) do 61 | @scheduler.schedule_periodic(-1, ->{}) 62 | end 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /test/rx/concurrency/test_scheduled_item.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | 5 | class DummyScheduler 6 | end 7 | 8 | class TestScheduledItem < Minitest::Test 9 | 10 | def setup 11 | @state = [] 12 | @item = Rx::ScheduledItem.new(DummyScheduler.new, @state, 5) do |_, state| 13 | state << 1 14 | end 15 | end 16 | 17 | def test_cancel 18 | assert_equal(false, @item.cancelled?) 19 | @item.cancel 20 | assert_equal(true, @item.cancelled?) 21 | end 22 | 23 | def test_invocation 24 | less = Rx::ScheduledItem.new(DummyScheduler.new, @state, 0) 25 | more = Rx::ScheduledItem.new(DummyScheduler.new, @state, 10) 26 | same = Rx::ScheduledItem.new(DummyScheduler.new, @state, 5) 27 | 28 | assert(less < @item) 29 | assert(more > @item) 30 | assert_equal(same, @item) 31 | end 32 | 33 | def test_invoke 34 | @item.invoke 35 | assert_equal([1], @state) 36 | end 37 | 38 | def test_invoke_raises_on_subsequent_calls 39 | @item.invoke 40 | assert_raises(RuntimeError) { @item.invoke } 41 | end 42 | 43 | def test_cancel_and_invoke 44 | assert_equal(false, @item.cancelled?) 45 | @item.cancel 46 | assert_equal(true, @item.cancelled?) 47 | @item.invoke 48 | assert_equal([], @state) 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/rx/concurrency/test_scheduler.rb: -------------------------------------------------------------------------------- 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 'test_helper' 5 | 6 | class MyScheduler 7 | include Rx::Scheduler 8 | 9 | attr_reader :now 10 | attr_accessor :wait_cycles 11 | 12 | def initialize(now = Time.now) 13 | @now = now 14 | @wait_cycles = 0 15 | @check = nil 16 | end 17 | 18 | def check(&action) 19 | @check = action 20 | end 21 | 22 | def schedule_with_state(state, action) 23 | action.call self, state 24 | end 25 | 26 | def schedule_relative_with_state(state, due_time, action) 27 | @check.call(lambda {|o| action.call(self, o)}, state, due_time) 28 | @wait_cycles += due_time 29 | action.call(self, state) 30 | end 31 | 32 | def schedule_absolute_with_state(state, due_time, action) 33 | self.schedule_relative_with_state(state, due_time - now, action) 34 | end 35 | end 36 | 37 | class TestBaseScheduler < Minitest::Test 38 | 39 | def setup 40 | @scheduler = MyScheduler.new 41 | end 42 | 43 | def test_now 44 | assert_equal(Time.now.to_i, Rx::Scheduler.now.to_i) 45 | end 46 | 47 | def test_schedule_absolute 48 | due = Time.now + 1 49 | ran = false 50 | task = ->() { ran = true } 51 | 52 | @scheduler.check { |a, s, t| assert_equal(1, t.to_i) } 53 | @scheduler.schedule_absolute(due, task) 54 | 55 | assert_equal(true, ran) 56 | assert_equal(1, @scheduler.wait_cycles.to_i) 57 | end 58 | 59 | def test_schedule_non_recursive 60 | ran = false 61 | @scheduler.schedule_recursive(->(a) { ran = true }) 62 | assert_equal(true, ran) 63 | end 64 | 65 | def test_schedule_recursive 66 | calls = 0 67 | task = ->(a) do 68 | calls += 1 69 | a.call if calls < 10 70 | end 71 | @scheduler.schedule_recursive(task) 72 | 73 | assert_equal(10, calls) 74 | end 75 | 76 | def test_schedule_recursive_absolute_non_recursive 77 | now = Time.now 78 | ran = false 79 | 80 | @scheduler.check { |a, s, t| assert_equal(0, t.to_i) } 81 | @scheduler.schedule_recursive_absolute(now, ->(a) { ran = true }) 82 | assert_equal(true, ran) 83 | assert_equal(0, @scheduler.wait_cycles.to_i) 84 | end 85 | 86 | def test_schedule_recursive_absolute_recursive 87 | now = Time.now 88 | calls = 0 89 | task = ->(a) do 90 | calls += 1 91 | a.call(now) if calls < 10 92 | end 93 | 94 | @scheduler.check { |a, s, t| assert_equal(0, t.to_i) } 95 | @scheduler.schedule_recursive_absolute(now, task) 96 | 97 | assert_equal(0, @scheduler.wait_cycles.to_i) 98 | assert_equal(10, calls) 99 | end 100 | 101 | def test_schedule_recursive_relative_non_recursive 102 | ran = false 103 | task = ->(a) { ran = true } 104 | 105 | @scheduler.check { |a, s, t| assert_equal(0, t.to_i) } 106 | @scheduler.schedule_recursive_relative(0, task) 107 | 108 | assert_equal(true, ran) 109 | assert_equal(0, @scheduler.wait_cycles) 110 | end 111 | 112 | def test_schedule_recursive_relative_recursive 113 | calls = 0 114 | task = ->(a) do 115 | calls += 1 116 | a.call(calls) if calls < 10 117 | end 118 | 119 | @scheduler.check { |a, s, t| assert_operator(t, :<, 10) } 120 | 121 | @scheduler.schedule_recursive_relative(0, task) 122 | assert_equal(45, @scheduler.wait_cycles) 123 | assert_equal(10, calls) 124 | end 125 | 126 | end 127 | -------------------------------------------------------------------------------- /test/rx/concurrency/test_virtual_time_scheduler.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | require 'rx/concurrency/helpers/historical_virtual_scheduler_helper' 5 | 6 | class TestVirtualTimeScheduler < Minitest::Test 7 | 8 | include HistoricalVirtualSchedulerTestHelper 9 | 10 | def setup 11 | @start = Time.now.to_i 12 | @scheduler = Rx::VirtualTimeScheduler.new(@start) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/rx/core/test_notification.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | 5 | class TestNotification < Minitest::Test 6 | include Rx::ReactiveTest 7 | 8 | def test_to_observable_empty 9 | scheduler = Rx::TestScheduler.new 10 | 11 | res = scheduler.configure do 12 | Rx::Notification.create_on_completed.to_observable(scheduler) 13 | end 14 | 15 | assert_messages [on_completed(201)], res.messages 16 | end 17 | 18 | def test_to_observable_just 19 | scheduler = Rx::TestScheduler.new 20 | 21 | res = scheduler.configure do 22 | Rx::Notification.create_on_next(42).to_observable(scheduler) 23 | end 24 | 25 | assert_messages [on_next(201, 42), on_completed(201)], res.messages 26 | end 27 | 28 | def test_to_observable_raise 29 | err = RuntimeError.new 30 | scheduler = Rx::TestScheduler.new 31 | 32 | res = scheduler.configure do 33 | Rx::Notification.create_on_error(err).to_observable(scheduler) 34 | end 35 | 36 | assert_messages [on_error(201, err)], res.messages 37 | end 38 | 39 | def test_notification_equality 40 | n = Rx::Notification.create_on_next(42) 41 | e = Rx::Notification.create_on_error(RuntimeError.new) 42 | c = Rx::Notification.create_on_completed 43 | 44 | n1 = n 45 | n2 = n 46 | e1 = e 47 | e2 = e 48 | c1 = c 49 | c2 = c 50 | 51 | assert(n1 == n2) 52 | assert(e1 == e2) 53 | assert(c1 == c2) 54 | 55 | assert(n1.eql? n2) 56 | assert(e1.eql? e2) 57 | assert(c1.eql? c2) 58 | end 59 | 60 | def test_on_next_initialize 61 | n = Rx::Notification.create_on_next(42) 62 | 63 | assert n.on_next? 64 | assert n.has_value? 65 | assert_equal 42, n.value 66 | end 67 | 68 | def test_on_next_equality 69 | n1 = Rx::Notification.create_on_next(42) 70 | n2 = Rx::Notification.create_on_next(42) 71 | n3 = Rx::Notification.create_on_next(24) 72 | n4 = Rx::Notification.create_on_completed 73 | 74 | assert(n1.eql? n1) 75 | assert(n1.eql? n2) 76 | refute(n1.eql? n3) 77 | refute(n3.eql? n1) 78 | refute(n1.eql? n4) 79 | refute(n4.eql? n1) 80 | end 81 | 82 | def test_on_next_to_s 83 | n = Rx::Notification.create_on_next(42) 84 | s = n.to_s 85 | 86 | assert (s.include? '42') 87 | assert (s.include? 'on_next') 88 | end 89 | 90 | class AcceptObserver 91 | include Rx::Observer 92 | 93 | def initialize 94 | @config = Rx::ObserverConfiguration.new 95 | yield @config 96 | end 97 | 98 | def on_next(value) 99 | @config.on_next_action.call value 100 | end 101 | 102 | def on_error(error) 103 | @config.on_error_action.call error 104 | end 105 | 106 | def on_completed 107 | @config.on_completed_action.call 108 | end 109 | end 110 | 111 | class CheckOnNextObserver 112 | attr_reader :value 113 | 114 | include Rx::Observer 115 | 116 | def on_next(value) 117 | @value = value 118 | end 119 | end 120 | 121 | def test_on_next_accept 122 | con = CheckOnNextObserver.new 123 | n1 = Rx::Notification.create_on_next(42) 124 | n1.accept(con) 125 | 126 | assert_equal 42, con.value 127 | end 128 | 129 | end 130 | -------------------------------------------------------------------------------- /test/rx/internal/test_priority_queue.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | 5 | class TestPriorityQueue < Minitest::Test 6 | 7 | def test_simple_push_and_shift 8 | queue = Rx::PriorityQueue.new 9 | queue.push 400 10 | 11 | assert_equal 400, queue.shift 12 | assert !queue.shift 13 | end 14 | 15 | def test_shift_with_priority 16 | queue = Rx::PriorityQueue.new 17 | queue.push 400 18 | queue.push 300 19 | queue.push 500 20 | 21 | assert_equal 300, queue.shift 22 | assert_equal 400, queue.shift 23 | assert_equal 500, queue.shift 24 | end 25 | 26 | def test_delete 27 | queue = Rx::PriorityQueue.new 28 | [1, 4, 5, 2, 3].each {|it| queue.push it } 29 | 30 | assert !queue.delete(404) 31 | assert queue.delete(3) 32 | assert_equal 1, queue.shift 33 | assert_equal 2, queue.shift 34 | assert_equal 4, queue.shift 35 | assert_equal 5, queue.shift 36 | end 37 | 38 | def test_push_thread_safety 39 | queue = Rx::PriorityQueue.new 40 | 5.times.map { 41 | Thread.new do 42 | 100.times do |i| 43 | queue.push i 44 | end 45 | end 46 | }.each(&:join) 47 | assert_equal 500, queue.length 48 | end 49 | 50 | def test_shift_thread_safety 51 | queue = Rx::PriorityQueue.new 52 | 500.times {|i| queue.push i } 53 | 54 | 5.times.map { 55 | Thread.new do 56 | 100.times do |i| 57 | queue.shift 58 | end 59 | end 60 | }.each(&:join) 61 | assert_equal 0, queue.length 62 | end 63 | 64 | def test_delete_same_item 65 | queue = Rx::PriorityQueue.new 66 | 10.times { queue.push 42 } 67 | queue.delete 42 68 | assert_equal 9, queue.length 69 | end 70 | 71 | end 72 | -------------------------------------------------------------------------------- /test/rx/linq/observable/test_sample.rb: -------------------------------------------------------------------------------- 1 | require "#{File.dirname(__FILE__)}/../../../test_helper" 2 | 3 | class TestObservableCreation < Minitest::Test 4 | include Rx::ReactiveTest 5 | 6 | def test_sampler_completes_first 7 | scheduler = Rx::TestScheduler.new 8 | 9 | res = scheduler.configure do 10 | scheduler.create_cold_observable( 11 | on_next(100, 1), 12 | on_next(200, 2), 13 | on_next(300, 3), 14 | on_next(400, 4), 15 | on_completed(600) 16 | ).sample( 17 | scheduler.create_cold_observable( 18 | on_next(50, 1), 19 | on_next(150, 1), 20 | on_next(350, 1), 21 | on_completed(400) 22 | ) 23 | ) 24 | end 25 | 26 | msgs = [ 27 | on_next(SUBSCRIBED + 150, 1), 28 | on_next(SUBSCRIBED + 350, 3), 29 | on_completed(600) 30 | ] 31 | assert_messages msgs, res.messages 32 | end 33 | 34 | def test_source_completes_first 35 | scheduler = Rx::TestScheduler.new 36 | 37 | res = scheduler.configure do 38 | scheduler.create_cold_observable( 39 | on_next(100, 1), 40 | on_completed(200) 41 | ).sample( 42 | scheduler.create_cold_observable( 43 | on_next(50, 1), 44 | on_next(150, 1), 45 | on_completed(400) 46 | ) 47 | ) 48 | end 49 | 50 | msgs = [ 51 | on_next(SUBSCRIBED + 150, 1), 52 | on_completed(400) 53 | ] 54 | assert_messages msgs, res.messages 55 | end 56 | 57 | def test_with_recipe 58 | scheduler = Rx::TestScheduler.new 59 | 60 | res = scheduler.configure do 61 | scheduler.create_cold_observable( 62 | on_next(100, 'left'), 63 | on_completed(200) 64 | ).sample( 65 | scheduler.create_cold_observable( 66 | on_next(150, 'right'), 67 | on_completed(200) 68 | ) 69 | ) { |left, right| [left, right] } 70 | end 71 | 72 | msgs = [ 73 | on_next(SUBSCRIBED + 150, ['left', 'right']), 74 | on_completed(400) 75 | ] 76 | assert_messages msgs, res.messages 77 | end 78 | 79 | def test_source_errors 80 | # Verify unsubscribe sampler 81 | scheduler = Rx::TestScheduler.new 82 | 83 | sampler = nil 84 | res = scheduler.configure do 85 | sampler = scheduler.create_cold_observable( 86 | on_next(50, 1), 87 | on_completed(200) 88 | ) 89 | 90 | scheduler.create_cold_observable( 91 | on_error(100, 'badness') 92 | ).sample(sampler) 93 | end 94 | 95 | msgs = [on_error(SUBSCRIBED + 100, 'badness')] 96 | assert_messages msgs, res.messages 97 | end 98 | 99 | def test_sampler_errors 100 | # Verify unsubscribe source 101 | scheduler = Rx::TestScheduler.new 102 | 103 | sampler = nil 104 | res = scheduler.configure do 105 | sampler = scheduler.create_cold_observable( 106 | on_error(50, 'badness'), 107 | on_completed(200) 108 | ) 109 | 110 | scheduler.create_cold_observable( 111 | on_next(100, 1) 112 | ).sample(sampler) 113 | end 114 | 115 | msgs = [on_error(250, 'badness')] 116 | assert_messages msgs, res.messages 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /test/rx/subscriptions/test_composite_subscription.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | 5 | class TestCompositeSubscription < Minitest::Test 6 | 7 | def test_include 8 | d1 = Rx::Subscription.create { } 9 | d2 = Rx::Subscription.create { } 10 | 11 | g = Rx::CompositeSubscription.new([d1, d2]) 12 | 13 | assert_equal 2, g.length 14 | assert g.include? d1 15 | assert g.include? d2 16 | end 17 | 18 | def test_to_a 19 | d1 = Rx::Subscription.create { } 20 | d2 = Rx::Subscription.create { } 21 | 22 | ds = [d1, d2] 23 | g = Rx::CompositeSubscription.new([d1, d2]) 24 | 25 | assert_equal 2, g.length 26 | 27 | x = g.to_a 28 | x.each_with_index do |item, index| 29 | assert_equal ds[index], item 30 | end 31 | end 32 | 33 | def test_push 34 | d1 = Rx::Subscription.create { } 35 | d2 = Rx::Subscription.create { } 36 | g = Rx::CompositeSubscription.new([d1]) 37 | 38 | assert_equal 1, g.length 39 | assert g.include? d1 40 | 41 | g.push d2 42 | 43 | assert_equal 2, g.length 44 | assert g.include? d2 45 | end 46 | 47 | def test_push_after_dispose 48 | disp1 = false 49 | disp2 = false 50 | 51 | d1 = Rx::Subscription.create { disp1 = true } 52 | d2 = Rx::Subscription.create { disp2 = true } 53 | g = Rx::CompositeSubscription.new [d1] 54 | assert_equal 1, g.length 55 | 56 | g.unsubscribe 57 | assert disp1 58 | assert_equal 0, g.length 59 | 60 | g.push d2 61 | assert disp2 62 | assert_equal 0, g.length 63 | 64 | assert g.unsubscribed? 65 | end 66 | 67 | def test_remove 68 | disp1 = false 69 | disp2 = false 70 | 71 | d1 = Rx::Subscription.create { disp1 = true } 72 | d2 = Rx::Subscription.create { disp2 = true } 73 | g = Rx::CompositeSubscription.new [d1, d2] 74 | 75 | assert_equal 2, g.length 76 | assert g.include? d1 77 | assert g.include? d2 78 | 79 | refute_nil g.delete d1 80 | assert_equal 1, g.length 81 | refute g.include? d1 82 | assert g.include? d2 83 | assert disp1 84 | 85 | refute_nil g.delete d2 86 | refute g.include? d1 87 | refute g.include? d2 88 | assert disp2 89 | 90 | disp3 = false 91 | d3 = Rx::Subscription.create { disp3 = true } 92 | assert_nil g.delete d3 93 | refute disp3 94 | end 95 | 96 | def test_clear 97 | disp1 = false 98 | disp2 = false 99 | 100 | d1 = Rx::Subscription.create { disp1 = true } 101 | d2 = Rx::Subscription.create { disp2 = true } 102 | g = Rx::CompositeSubscription.new [d1, d2] 103 | assert_equal 2, g.length 104 | 105 | g.clear 106 | assert disp1 107 | assert disp2 108 | assert_equal 0, g.length 109 | 110 | disp3 = false 111 | d3 = Rx::Subscription.create { disp3 = true } 112 | g.push d3 113 | refute disp3 114 | assert_equal 1, g.length 115 | end 116 | end 117 | -------------------------------------------------------------------------------- /test/rx/subscriptions/test_serial_subscription.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | 5 | class TestSerialSubscription < Minitest::Test 6 | 7 | def test_ctor 8 | d = Rx::SerialSubscription.new 9 | assert_nil d.subscription 10 | end 11 | 12 | def test_replace_before_dispose 13 | disp1 = false 14 | disp2 = false 15 | 16 | m = Rx::SerialSubscription.new 17 | d1 = Rx::Subscription.create { disp1 = true } 18 | m.subscription = d1 19 | assert_same d1, m.subscription 20 | refute disp1 21 | 22 | d2 = Rx::Subscription.create { disp2 = true } 23 | m.subscription = d2 24 | assert_same d2, m.subscription 25 | assert disp1 26 | refute disp2 27 | end 28 | 29 | def test_replace_after_dispose 30 | disp1 = false 31 | disp2 = false 32 | 33 | m = Rx::SerialSubscription.new 34 | m.unsubscribe 35 | assert m.unsubscribed? 36 | 37 | d1 = Rx::Subscription.create { disp1 = true } 38 | m.subscription = d1 39 | assert_nil m.subscription 40 | assert disp1 41 | 42 | d2 = Rx::Subscription.create { disp2 = true } 43 | m.subscription = d2 44 | assert_nil m.subscription 45 | assert disp2 46 | end 47 | 48 | def test_dispose 49 | disp = false 50 | 51 | m = Rx::SerialSubscription.new 52 | d = Rx::Subscription.create { disp = true } 53 | m.subscription = d 54 | assert_same d, m.subscription 55 | refute disp 56 | 57 | m.unsubscribe 58 | assert m.unsubscribed? 59 | assert disp 60 | end 61 | 62 | end 63 | -------------------------------------------------------------------------------- /test/rx/subscriptions/test_singleassignment_subscription.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | 5 | class TestSingleAssignmentSubscription < Minitest::Test 6 | 7 | def test_subscription_null 8 | d = Rx::SingleAssignmentSubscription.new 9 | d.subscription = nil 10 | 11 | assert_nil d.subscription 12 | end 13 | 14 | def test_dispose_after_set 15 | unsubscribed = false 16 | 17 | d = Rx::SingleAssignmentSubscription.new 18 | dd = Rx::Subscription.create { unsubscribed = true } 19 | d.subscription = dd 20 | 21 | assert_same dd, d.subscription 22 | 23 | refute unsubscribed 24 | 25 | d.unsubscribe 26 | 27 | assert unsubscribed 28 | 29 | d.unsubscribe 30 | 31 | assert unsubscribed 32 | assert d.unsubscribed? 33 | end 34 | 35 | def test_dispose_before_set 36 | unsubscribed = false 37 | 38 | d = Rx::SingleAssignmentSubscription.new 39 | dd = Rx::Subscription.create { unsubscribed = true } 40 | 41 | refute unsubscribed 42 | d.unsubscribe 43 | refute unsubscribed 44 | assert d.unsubscribed? 45 | 46 | d.subscription = dd 47 | assert unsubscribed 48 | assert d.subscription.nil? 49 | d.unsubscribe #should be noop 50 | 51 | assert unsubscribed 52 | end 53 | 54 | def test_dispose_multiple_times 55 | d = Rx::SingleAssignmentSubscription.new 56 | d.subscription = Rx::Subscription.empty 57 | 58 | assert_raises(RuntimeError) { d.subscription = Rx::Subscription.empty } 59 | end 60 | 61 | end 62 | -------------------------------------------------------------------------------- /test/rx/subscriptions/test_subscription.rb: -------------------------------------------------------------------------------- 1 | # Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. 2 | 3 | require 'test_helper' 4 | 5 | class TestSubscription < Minitest::Test 6 | 7 | def test_disposable_create 8 | d = Rx::Subscription.create { } 9 | refute_nil d 10 | end 11 | 12 | def test_create_dispose 13 | unsubscribed = false 14 | d = Rx::Subscription.create { unsubscribed = true } 15 | refute unsubscribed 16 | 17 | d.unsubscribe 18 | assert unsubscribed 19 | end 20 | 21 | def test_empty 22 | d = Rx::Subscription.empty 23 | refute_nil d 24 | d.unsubscribe 25 | end 26 | 27 | end 28 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | if ENV['COVERAGE'] 2 | require 'simplecov' 3 | 4 | SimpleCov.start do 5 | coverage_dir '.coverage' 6 | add_filter 'test/' 7 | end 8 | end 9 | 10 | require 'minitest/autorun' 11 | require 'rx' 12 | --------------------------------------------------------------------------------