opal-async 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a38a4215b9b7cdde96a1e4d00c08c5940bd829c052281b93ba7286c31bab856a
4
+ data.tar.gz: d329ce4369abd51128b468dc61fe26266fd96590688f55a6e2d934b631da831c
5
+ SHA512:
6
+ metadata.gz: 7d2373f2972e5402a14523f2975d544a2ba0faa4a2e68e527e4d27c3b327d279b687cccdda0b80ec34389149c0fa6d418e8faa04d9fc4b68f8f0ae96b679e1d4
7
+ data.tar.gz: 13b4c95d7c39c6f5d7a0497156e40f72fc01ae96f5e32707a09b230bc3c2e81d803aae4bfd94315f956b005b892855aacdf59670824b5de99e3aa15c530f36ff
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015-2020 Ben Titcomb & Andy Maleh
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,167 @@
1
+ # Opal: Async
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ gem 'opal-async', github: "ravenstine/opal-async
8
+
9
+ And then execute:
10
+
11
+ $ bundle install
12
+
13
+ Or install it yourself as:
14
+
15
+ $ gem install opal-async
16
+
17
+ Then require 'opal-async' in both your Opal code and your Opal compilation environment.
18
+
19
+
20
+ ## Usage
21
+
22
+ #### Enumerator
23
+
24
+ The enumerator provides iteration methods for any enumerable object. These methods are 'non-blocking', so other operations in the event loop can continue to be executed in between iterations. Beware, this is not faster than a normal blocking iteration; it is trading off performance for not blocking other operations you may want to have continue such as UI updates & camera frame capture. Very large arrays will take a long time to finish while the overhead may not be noticeable for smaller arrays. It is best to do some tests and assess whether the trade-off is balanced enough for your needs.
25
+
26
+ Methods can be chained and when the enumerator is finished, a promise is executed using #done.
27
+
28
+ For example:
29
+
30
+ ```ruby
31
+ require 'opal-async'
32
+ enumerator = Async::Enumerator.new([1,2,3,4,5,6,7,8,9])
33
+ enumerator.map{|x| x + 2}.done{|x| puts x}
34
+ #=> [3,4,5,6,7,8,9,10,11]
35
+ ```
36
+
37
+ Here's an example of method-chaining:
38
+ ```ruby
39
+ enumerator = Async::Enumerator.new([1,2,3,4,5,6,7,8,9])
40
+ enumerator.map{|x| x + 2}.each_slice(3).each{|x| puts x}
41
+ #=> [3,4,5]
42
+ #=> [6,7,8]
43
+ #=> [9,10,11]
44
+ ```
45
+
46
+ ##### Available enumerator methods:
47
+ - each
48
+ - map
49
+ - each_slice
50
+ - select
51
+ - reject
52
+
53
+ #### Task
54
+ A task contains code that will be added to the call stack of the event loop. The Enumerator uses tasks to run small chunks of code without blocking the event loop. A task can do the same things that a Timeout or an Interval can do but with some added features and optimizations.
55
+
56
+ With no options provided, a task will be run immediately once the event loop comes back to it(if the environment supports this). If the environment does not support immediates, it will attempt to polyfill an immediate before falling back on a 0ms timeout.
57
+
58
+ Example:
59
+
60
+ ```
61
+ Async::Task.new do
62
+ puts "hello world"
63
+ end
64
+
65
+ #=> hello world
66
+ ```
67
+
68
+ By default, a task will only run once. To make a task repeat, set the option times to however many times you want the task to repeat. You can also have access to countup and countdown variables.
69
+
70
+ ```
71
+ Async::Task.new do times: 5 do |countup, countdown|
72
+ puts countdown
73
+ end
74
+
75
+ #=> 5
76
+ #=> 4
77
+ #=> 3
78
+ #=> 2
79
+ #=> 1
80
+ ```
81
+
82
+ To make a task repeat infinitely, set times to ```:infinite```, or repeat to ```true```. A countup will be provided but no countdown. You can also use ```:i``` for short.
83
+
84
+ ```
85
+ Async::Task.new times: :infinite do
86
+ puts "forever"
87
+ end
88
+
89
+ #=> forever
90
+ #=> forever
91
+ #=> forever
92
+ ...
93
+
94
+ ```
95
+
96
+ The step option will determine how much you want your task to "step".
97
+
98
+ ```
99
+ Async::Task.new times: 10, step: 2 do |countup, countdown|
100
+ puts countup
101
+ end
102
+
103
+ #=> 0
104
+ #=> 2
105
+ #=> 4
106
+ #=> 6
107
+ #=> 8
108
+ ```
109
+
110
+ To set a delay time on your task, specify the delay option with the number of milliseconds you want the duration of the delay to be. This can also be done when you have set your task to repeat.
111
+
112
+ ```
113
+ Async::Task.new delay: 1000 do
114
+ puts "this took 1 second"
115
+ end
116
+ ```
117
+
118
+ The delay and steps of a task can be modified within the execution of the task. The following example will start out slow and increase in speed:
119
+
120
+ ```
121
+ task = Async::Task.new times: 5, delay: 5000 do |countup, countdown|
122
+ puts countdown
123
+ task.delay = task.delay - 1000
124
+ end
125
+ ```
126
+
127
+ Tasks also have callbacks that can be performed on certain events.
128
+
129
+ Here is an example of how to execute code after a repeating task has finished:
130
+
131
+ ```
132
+ task = Async::Task.new times: 3, delay: 1000 do |countup, countdown|
133
+ puts countdown
134
+ end
135
+
136
+ task.on_finish {puts "BOOM"}
137
+
138
+ #=> 3
139
+ #=> 2
140
+ #=> 1
141
+ #=> BOOM
142
+ ```
143
+
144
+ Other callbacks include ```on_start``` and ```on_stop```.
145
+
146
+
147
+ #### Other Timers
148
+
149
+ You can also timeouts and intervals specifically:
150
+
151
+
152
+ ```
153
+ Timeout.new 3000 do
154
+ puts "I just waited 3 seconds."
155
+ end
156
+ ```
157
+
158
+ ```
159
+ Interval.new 3000 do
160
+ puts "I'm going to do this every 3 seconds."
161
+ end
162
+ ```
163
+
164
+ ## Contributors
165
+
166
+ - [Benjamin Titcomb](https://github.com/Ravenstine) (Creator and Main Contributor)
167
+ - [Andy Maleh](https://github.com/AndyObtiva) (Gemifier)
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.1.0
@@ -0,0 +1 @@
1
+ require 'opal/async'
@@ -0,0 +1,4 @@
1
+ require 'opal'
2
+
3
+ # Just register our opal code path with opal build tools
4
+ Opal.append_path File.expand_path('../../../opal', __FILE__)
@@ -0,0 +1,7 @@
1
+ module Async
2
+ end
3
+ require 'async/timeout'
4
+ require 'async/interval'
5
+ require 'async/countdown'
6
+ require 'async/task'
7
+ require 'async/enumerator'
@@ -0,0 +1,31 @@
1
+ module Async
2
+ class Countdown
3
+ def initialize time=0, intervals=1, step=1, &block
4
+ @countdown = intervals
5
+ @countup = 0
6
+ @step = step
7
+ @block = block
8
+ @time = time
9
+ start
10
+ end
11
+
12
+ def start
13
+ @interval = `setInterval(function(){#{@block.call(@countdown,@countup)};#{@countdown -= @step};#{@countup += @step};#{stop if @countdown.zero?};}, #{@time})`
14
+ @stopped = false
15
+ end
16
+
17
+ def stop
18
+ `clearInterval(#@interval)`
19
+ @stopped = true
20
+ end
21
+
22
+ def restart
23
+ stop
24
+ start
25
+ end
26
+
27
+ def stopped?
28
+ @stopped = true
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,164 @@
1
+ require 'native'
2
+
3
+ module Async
4
+ class Enumerator
5
+ def initialize(enumerable, options={})
6
+ puts 'initialize enumerator'
7
+ @options = options
8
+ @enumerable = enumerable
9
+ @jobs = []
10
+ @jobs_finished = 0
11
+ @jobs_queued = 0
12
+ @lock = false
13
+ set_defaults
14
+ end
15
+
16
+ def locked?
17
+ !@lock
18
+ end
19
+
20
+ def unlocked?
21
+ !@lock
22
+ end
23
+
24
+ def lock
25
+ @lock = true
26
+ end
27
+
28
+ def unlock
29
+ @lock = false
30
+ end
31
+
32
+ def queue(proc)
33
+ @jobs << proc
34
+ @jobs_queued += 1
35
+ watcher = Task.new times: :infinite do
36
+ if unlocked?
37
+ lock
38
+ @jobs.first.call
39
+ watcher.stop
40
+ elsif @jobs.count.zero?
41
+ watcher.stop
42
+ end
43
+ end
44
+ end
45
+
46
+ def set_defaults
47
+ @output = []
48
+ @finished = false
49
+ @length = @enumerable.length
50
+ end
51
+ def each step=1, &block
52
+ proc = Proc.new do
53
+ set_defaults
54
+ @step = step
55
+ Task.new times: @length, step: @step do |ind, cdown|
56
+ Task.new do
57
+ yield(@enumerable[ind], ind)
58
+ finish_job if cdown <= 1
59
+ end
60
+ end
61
+ @output = @enumerable
62
+ end
63
+ queue proc
64
+ self
65
+ end
66
+ def each_slice step=1, &block
67
+ proc = Proc.new do
68
+ set_defaults
69
+ @step = step
70
+ chunk = []
71
+ block_was_given = block_given?
72
+ Task.new times: @length do |ind, cdown|
73
+ Task.new do
74
+ chunk << @enumerable[ind]
75
+ if (ind + 1) % @step == 0
76
+ if block_was_given
77
+ @output << yield(@enumerable[ind], ind)
78
+ else
79
+ @output << chunk
80
+ end
81
+ chunk = []
82
+ end
83
+ if cdown <= 1
84
+ finish_job
85
+ end
86
+ end
87
+ end
88
+ end
89
+ queue proc
90
+ self
91
+ end
92
+ def map step=1, &block
93
+ proc = Proc.new do
94
+ set_defaults
95
+ @step = step
96
+ Task.new step: @step, times: @length do |ind, cdown|
97
+ Task.new do
98
+ @output[ind] = yield(@enumerable[ind], ind)
99
+ if cdown <= 1
100
+ finish_job
101
+ end
102
+ end
103
+ end
104
+ end
105
+ queue proc
106
+ self
107
+ end
108
+ def select step=1, &block
109
+ proc = Proc.new do
110
+ set_defaults
111
+ @step = step
112
+ Task.new step: @step, times: @length do |ind, cdown|
113
+ Task.new do
114
+ operation = yield(@enumerable[ind], ind)
115
+ @output << @enumerable[ind] if operation == true
116
+ if cdown <= 1
117
+ finish_job
118
+ end
119
+ end
120
+ end
121
+ end
122
+ queue proc
123
+ self
124
+ end
125
+ def reject step=1, &block
126
+ proc = Proc.new do
127
+ set_defaults
128
+ @step = step
129
+ Task.new step: @step, times: @length do |ind, cdown|
130
+ Task.new do
131
+ operation = yield(@enumerable[ind], ind)
132
+ @output << @enumerable[ind] unless operation == true
133
+ if cdown <= 1
134
+ finish_job
135
+ end
136
+ end
137
+ end
138
+ end
139
+ queue proc
140
+ self
141
+ end
142
+ def done &block
143
+ complete Proc.new {yield(@output, self)}
144
+ end
145
+ def then &block
146
+ complete Proc.new {yield(self.class.new(@output))}
147
+ end
148
+ def finish_job
149
+ @enumerable = @output
150
+ @jobs.shift
151
+ @jobs_finished += 1
152
+ unlock
153
+ end
154
+ def complete proc
155
+ checker = Task.new times: :infinite do
156
+ if (@jobs_finished >= @jobs_queued)
157
+ checker.stop
158
+ proc.call
159
+ end
160
+ end
161
+ self
162
+ end
163
+ end
164
+ end
@@ -0,0 +1,28 @@
1
+ module Async
2
+ class Interval
3
+ def initialize time=0, &block
4
+ @block = block
5
+ @time = time
6
+ start
7
+ end
8
+
9
+ def stop
10
+ `clearInterval(#{@interval})`
11
+ @stopped = true
12
+ end
13
+
14
+ def start
15
+ @interval = `setInterval(function(){#{@block.call}}, #{@time})`
16
+ @stopped = false
17
+ end
18
+
19
+ def stopped?
20
+ @stopped
21
+ end
22
+
23
+ def restart
24
+ stop
25
+ start
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,155 @@
1
+ module Async
2
+ class Task
3
+ attr_accessor :delay, :times
4
+ def initialize options={}, &block
5
+ @options = options
6
+ @block = block
7
+ generate_settings
8
+ start
9
+ end
10
+
11
+ def generate_settings
12
+ @countdown = @options[:times] || 1
13
+ @countup = 0
14
+ @step = @options[:step] || 1
15
+ @delay = @options[:delay] || 0
16
+ @times = @options[:times]
17
+ @proc = Proc.new do
18
+ if @times
19
+ if @times.is_a?(Fixnum)
20
+ @block.call(@countup, @countdown)
21
+ @countdown -= @step
22
+ unless @countdown <= 0
23
+ start unless @stopped
24
+ else
25
+ stop
26
+ @after_finish.call(@enumerable) if @after_finish
27
+ end
28
+ elsif [:infinite, :unlimited, :indefinite, :i].include?(@times) || @options[:repeat]
29
+ @block.call(@countup, nil)
30
+ start unless @stopped
31
+ end
32
+ @countup += @step
33
+ else
34
+ @block.call
35
+ @stopped = true
36
+ end
37
+ end
38
+ end
39
+
40
+ def on_stop &block
41
+ @after_stop = block
42
+ end
43
+
44
+ def on_start &block
45
+ @before_start = block
46
+ end
47
+
48
+ def on_finish &block
49
+ @after_finish = block
50
+ end
51
+
52
+ def stopped?
53
+ @stopped
54
+ end
55
+
56
+ def stop
57
+ @stopper.call if @stopper
58
+ @after_stop.call(@enumerable) if @after_stop
59
+ @stopped = true
60
+ end
61
+
62
+ def restart
63
+ stop
64
+ generate_settings
65
+ start
66
+ end
67
+
68
+ def start
69
+ @before_start.call(@enumerable) if @before_start
70
+ if @delay && @delay > 0
71
+ set_timeout
72
+ else
73
+ set_immediate
74
+ end
75
+ @stopped = false
76
+ end
77
+
78
+ def set_timeout
79
+ @task = `setTimeout(function() {
80
+ #{@proc.call};
81
+ }, #{@delay || 0})`
82
+
83
+ @stopper = Proc.new{`window.clearTimeout(#@task)`}
84
+ end
85
+
86
+ def set_immediate
87
+ if `window.setImmediate != undefined`
88
+ @task = `window.setImmediate(function() {
89
+ #{@proc.call};
90
+ })`
91
+
92
+
93
+ @stopper = Proc.new{`window.clearImmediate(#@task)`}
94
+
95
+ elsif `window.msSetImmediate != undefined`
96
+ @task = `window.msSetImmediate(function() {
97
+ #{@proc.call};
98
+ })`
99
+
100
+
101
+ @stopper = Proc.new{`window.msClearImmediate(#@task)`}
102
+
103
+ elsif `window.MessageChannel != undefined`
104
+ ### Higher precedence than postMessage because it is supported in WebWorkers.
105
+ `
106
+ var task = function(){
107
+ if(#{!stopped?}){
108
+ #{@proc.call};
109
+ };
110
+ };
111
+ var mc = new MessageChannel();
112
+
113
+ mc.port1.onmessage = function(){
114
+ if (!#{stopped?}){
115
+ task.apply(task)
116
+ }
117
+ };
118
+ mc.port2.postMessage(null);
119
+ `
120
+
121
+ @stopper = Proc.new{}
122
+ elsif `window.postMessage != undefined`
123
+ @message_id = "opal.async.task.#{rand(1_000_000)}."
124
+ @task = `window.addEventListener("message", function(event){
125
+ if((event.data === '#{@message_id}') && #{!stopped?}){
126
+ #{@proc.call};
127
+ }
128
+ }, true)`
129
+
130
+ `window.postMessage("#{@message_id}", "*")`
131
+
132
+ @stopper = Proc.new{@message_id = nil}
133
+ elsif `document != undefined` && `document.onreadystatechange === null`
134
+ `
135
+ var script = document.createElement("script");
136
+ script.onreadystatechange = function() {
137
+ if (!#{stopped?}) {
138
+ #{@proc.call};
139
+ }
140
+ script.onreadystatechange = null;
141
+ script.parentNode.removeChild(script);
142
+ };
143
+ document.documentElement.appendChild(script);
144
+ `
145
+ @stopper = Proc.new{}
146
+ else
147
+ @task = `setTimeout(function() {
148
+ #{@proc.call};
149
+ }, 0)`
150
+ end
151
+
152
+ end
153
+
154
+ end
155
+ end
@@ -0,0 +1,30 @@
1
+ module Async
2
+ class Timeout
3
+
4
+ def initialize time=0, &block
5
+ @time = time
6
+ @block = block
7
+ start
8
+ end
9
+
10
+ def stopped?
11
+ @stopped
12
+ end
13
+
14
+ def stop
15
+ `clearTimeout(#@timeout)`
16
+ @stopped = true
17
+ end
18
+
19
+ def restart
20
+ stop
21
+ start
22
+ end
23
+
24
+ def start
25
+ @timeout = `setTimeout(function(){#{@block.call};#{@stopped = true}}, #{@time})`
26
+ @stopped = false
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1 @@
1
+ require 'async'
metadata ADDED
@@ -0,0 +1,154 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: opal-async
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ravenstine
8
+ - Andy Maleh
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2020-06-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: opal
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 1.0.0
21
+ - - "<"
22
+ - !ruby/object:Gem::Version
23
+ version: 2.0.0
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: 1.0.0
31
+ - - "<"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.0
34
+ - !ruby/object:Gem::Dependency
35
+ name: opal-rspec
36
+ requirement: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ type: :development
42
+ prerelease: false
43
+ version_requirements: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ - !ruby/object:Gem::Dependency
49
+ name: rdoc
50
+ requirement: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.12'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.12'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ type: :development
70
+ prerelease: false
71
+ version_requirements: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '1.0'
76
+ - !ruby/object:Gem::Dependency
77
+ name: juwelier
78
+ requirement: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 2.1.0
83
+ type: :development
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 2.1.0
90
+ - !ruby/object:Gem::Dependency
91
+ name: simplecov
92
+ requirement: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ type: :development
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ description: Provides non-blocking tasks and enumerators for Opal.
105
+ email:
106
+ - benjamin@pixelstreetinc.com
107
+ - andy.am@gmail.com
108
+ executables: []
109
+ extensions: []
110
+ extra_rdoc_files:
111
+ - LICENSE.txt
112
+ - README.md
113
+ files:
114
+ - LICENSE.txt
115
+ - README.md
116
+ - VERSION
117
+ - lib/opal-async.rb
118
+ - lib/opal/async.rb
119
+ - opal/async.rb
120
+ - opal/async/countdown.rb
121
+ - opal/async/enumerator.rb
122
+ - opal/async/interval.rb
123
+ - opal/async/task.rb
124
+ - opal/async/timeout.rb
125
+ - opal/opal-async.rb
126
+ homepage: http://github.com/AndyObtiva/opal-async
127
+ licenses:
128
+ - MIT
129
+ metadata: {}
130
+ post_install_message:
131
+ rdoc_options:
132
+ - "--main"
133
+ - README
134
+ - "--line-numbers"
135
+ - "--include"
136
+ - opal
137
+ require_paths:
138
+ - lib
139
+ required_ruby_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ required_rubygems_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ requirements: []
150
+ rubygems_version: 3.1.2
151
+ signing_key:
152
+ specification_version: 4
153
+ summary: Provides non-blocking tasks and enumerators for Opal.
154
+ test_files: []