fiber_stream 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2401496eff99cd4792deda8fa927688fc3ba0e97e8a35637db4395895ab04cd9
4
- data.tar.gz: 9f9674e2bc7dc9ce49b899727a02b9158ce6c9629c744ec6d626b16923e14160
3
+ metadata.gz: 504a7400182e09bb66a5a07981bbb2cfab350397d09c23ea036e856f29afe1d2
4
+ data.tar.gz: 67c7ddead203d42bf869dcba7d840e7d09514d888b90b84b77d97a8cedc19e1e
5
5
  SHA512:
6
- metadata.gz: e7237c7c15b66105b09cdccf1e52980f3c030c74acbd2dc7cc3b5cfb720a716836925b5801bdc59351131dcd7ecab72ebdedb6b50593f5258a1cff2d0ebb39a9
7
- data.tar.gz: d9bc7c85992c344bef4a36b0f234840c154c06274c450ef0696246d3042a017f8764c22ab1a2df43e3059420c52af7827852e63b4a5e91d668f4c9bb6a191d57
6
+ metadata.gz: f9c8d80faca53d9c6e059326bc9e6ffe7497aa8a6cbcad3b42eefe0ef3a3d8d29f08a3afbec8fc2b02b2064a745bb00ea644e816f913aa624a2ad11f1b087b99
7
+ data.tar.gz: 9e6f87b74cbfe2ccaaa9c32990f9786af3ee3ee3d7f2c3dd605a88efa8f3e436e4a1051d65c1eb1664859af212a2fecc82c66dc585cf9333dd799917c2a9035e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,53 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0 - 2026-06-21
4
+
5
+ ### Added
6
+
7
+ - `Flow.tap { |element| ... }` for lazy pass-through observation without
8
+ changing emitted elements.
9
+ - `Flow.filter_map { |element| ... }` and `Source#filter_map` for combined
10
+ transformation and falsey-value dropping.
11
+ - `Flow.reject { |element| ... }` and `Source#reject` for complement
12
+ predicate filtering.
13
+ - `Flow.compact` and `Source#compact` for nil-only filtering while preserving
14
+ `false`.
15
+ - `Flow.map_concat { |element| enumerable }` and `Source#map_concat` for
16
+ one-to-many element expansion.
17
+ - `Flow.throttle(...)`, `Source#throttle(...)`, and `RateLimiter` for
18
+ pull-driven rate limiting with optional shared quota state.
19
+ - `Sink.count` for counting stream elements without accumulating them.
20
+
21
+ ### Changed
22
+
23
+ - Expanded README and website reference coverage for the flow operators and
24
+ rate limiter added in 0.5.0.
25
+ - Promoted completed flow product specs and design docs from draft to accepted
26
+ status.
27
+
28
+ ## 0.4.0 - 2026-06-09
29
+
30
+ ### Added
31
+
32
+ - `Flow.parallel_unordered_map(concurrency:)` and
33
+ `Source#parallel_unordered_map(concurrency:)` for scheduler-backed mapping
34
+ that emits results in completion order instead of preserving input order.
35
+ - `Source.ractor_producer` for FiberStream-owned single producer Ractors with
36
+ one-outstanding-ack backpressure and cooperative cleanup.
37
+ - `Source.ractor_merge_producers` for ready-order fan-in from multiple
38
+ FiberStream-owned producer Ractors without requiring a `Fiber.scheduler`.
39
+ - `Flow.scan(initial)` and `Source#scan(initial)` for lazy running
40
+ accumulators using `Sink.fold`-style reducer semantics.
41
+
42
+ ### Changed
43
+
44
+ - Updated README and website reference coverage for owned Ractor producers,
45
+ unordered parallel mapping, and scan.
46
+ - Prefer high-level owned Ractor producer examples in user-facing
47
+ documentation while keeping low-level port APIs documented for externally
48
+ owned producers.
49
+ - Updated the project Ruby pin to 4.0.5.
50
+
3
51
  ## 0.3.0 - 2026-06-06
4
52
 
5
53
  ### Added
data/README.md CHANGED
@@ -1,12 +1,15 @@
1
1
  # FiberStream
2
2
 
3
- FiberStream is an early-stage Ruby library for linear, pull-based stream
4
- processing with backpressure.
3
+ FiberStream is a Ruby library for linear stream processing with pull-based
4
+ backpressure.
5
5
 
6
- Build a lazy `Source`, transform it with `Flow` stages, and materialize it with
7
- a `Sink`.
6
+ It builds lazy `Source` definitions, transforms values with `Flow` stages, and
7
+ materializes results with `Sink` objects.
8
+
9
+ [![Gem Version](https://badge.fury.io/rb/fiber_stream.svg)](https://badge.fury.io/rb/fiber_stream)
8
10
 
9
11
  ## Quick Start
12
+ Please see the project [documentation](https://dakatsuka.github.io/fiber_stream/) for more details.
10
13
 
11
14
  ```ruby
12
15
  require "fiber_stream"
@@ -27,12 +30,15 @@ FiberStream currently supports linear pipelines only.
27
30
 
28
31
  Implemented capabilities:
29
32
 
30
- - in-memory, IO, backpressure-aware Ractor port, and Ractor port merge sources
33
+ - in-memory, IO, FiberStream-owned Ractor producer, backpressure-aware Ractor
34
+ port, and Ractor port merge sources
31
35
  - lazy source concatenation, zipping, and scheduler-backed merging
32
- - mapping, filtering, limiting, predicate-based limiting and dropping,
33
- fixed-prefix dropping, fixed-size grouping, line splitting, buffering, async
34
- boundaries, ordered parallel mapping, and ordered Ractor-backed mapping
35
- - array, first-element, fold, foreach, and IO sinks
36
+ - mapping, filtering, transform-and-filter, nil compaction, side-effect
37
+ observation, one-to-many expansion, limiting, predicate-based limiting and
38
+ dropping, fixed-prefix dropping, fixed-size grouping, line splitting,
39
+ buffering, async boundaries, throttling, ordered and unordered parallel
40
+ mapping, and ordered Ractor-backed mapping
41
+ - array, first-element, count, fold, foreach, and IO sinks
36
42
  - reusable flow composition and runnable pipelines
37
43
  - foreground and scheduler-backed background pipeline execution
38
44
  - public RBS signatures
@@ -96,64 +102,51 @@ chunks =
96
102
  end.wait
97
103
  ```
98
104
 
99
- Ractor port sources connect a producer Ractor with an explicit ack handshake.
100
- The producer creates its acknowledgment port, waits for `RactorPort::Ack`, and
101
- then sends one typed message back to the FiberStream data port:
105
+ Owned Ractor producer sources run producer blocks in FiberStream-managed
106
+ Ractors. The producer block receives a `RactorProducer` context and emits one
107
+ value per downstream demand:
102
108
 
103
109
  ```ruby
104
- data_port = Ractor::Port.new
105
- setup_port = Ractor::Port.new
106
-
107
- producer =
108
- Ractor.new(data_port, setup_port) do |outbox, setup|
109
- ack_port = Ractor::Port.new
110
- setup.send(ack_port)
111
-
112
- values = [1, 2, 3].to_enum
113
-
114
- loop do
115
- case ack_port.receive
116
- in FiberStream::RactorPort::Ack
117
- begin
118
- outbox.send(FiberStream::RactorPort::Element.new(values.next))
119
- rescue StopIteration
120
- outbox.send(FiberStream::RactorPort::Complete.new)
121
- break
122
- end
123
- in FiberStream::RactorPort::Cancel
124
- break
125
- end
110
+ PRODUCE_VALUES =
111
+ Ractor.shareable_proc do |producer, values|
112
+ values.each do |value|
113
+ break unless producer.emit(value)
126
114
  end
127
115
  end
128
116
 
129
- ack_port = setup_port.receive
130
-
131
- FiberStream::Source.ractor_port(data_port, ack_port: ack_port)
117
+ FiberStream::Source.ractor_producer([1, 2, 3], &PRODUCE_VALUES)
132
118
  .run_with(FiberStream::Sink.to_a)
133
119
  # => [1, 2, 3]
134
-
135
- producer.value
136
120
  ```
137
121
 
138
- `RactorPort::Failure` cause metadata is producer-provided and is surfaced on
139
- `RactorPortSourceError`. Redact internal paths, secrets, tenant data, or other
140
- sensitive details before sending failures across trust boundaries.
141
-
142
- Multiple producer Ractors can be merged directly without a scheduler-backed
143
- `Source#merge`. Each producer still receives at most one outstanding ack:
122
+ Multiple owned producer Ractors can be merged directly without a
123
+ scheduler-backed `Source#merge`. Each producer still receives at most one
124
+ outstanding ack:
144
125
 
145
126
  ```ruby
127
+ PRODUCE_TAGGED_VALUES =
128
+ Ractor.shareable_proc do |producer, tag, values|
129
+ values.each do |value|
130
+ break unless producer.emit([tag, value])
131
+ end
132
+ end
133
+
146
134
  source =
147
- FiberStream::Source.ractor_merge_ports(
148
- [
149
- { port: data_port_a, ack_port: ack_port_a },
150
- { port: data_port_b, ack_port: ack_port_b }
151
- ]
152
- )
135
+ FiberStream::Source.ractor_merge_producers do |group|
136
+ group.producer(:a, [1, 2], &PRODUCE_TAGGED_VALUES)
137
+ group.producer(:b, [3, 4], &PRODUCE_TAGGED_VALUES)
138
+ end
153
139
 
154
- values = source.run_with(FiberStream::Sink.to_a)
140
+ source.run_with(FiberStream::Sink.to_a)
141
+ # Example result: [[:a, 1], [:b, 3], [:a, 2], [:b, 4]]
155
142
  ```
156
143
 
144
+ Use the lower-level `Source.ractor_port` and `Source.ractor_merge_ports` APIs
145
+ when producer Ractors are owned outside FiberStream or need custom lifecycle
146
+ handling. `RactorPort::Failure` cause metadata is producer-provided and is
147
+ surfaced on `RactorPortSourceError`; redact sensitive details before sending
148
+ failures across trust boundaries.
149
+
157
150
  Streaming HTTP response bodies that implement `#each`, such as
158
151
  `async-http` response bodies, can be used with `Source.each` without buffering
159
152
  the full body first. Use the HTTP client's block form or an explicit `ensure`
@@ -216,6 +209,62 @@ FiberStream::Source.each([" a ", "", " b "])
216
209
  # => ["a", "b"]
217
210
  ```
218
211
 
212
+ Use `reject` when the predicate names values to drop. Truthy predicate results
213
+ drop the original element; `false` and `nil` results pass it through unchanged:
214
+
215
+ ```ruby
216
+ result =
217
+ FiberStream::Source.each([1, 2, 3, 4])
218
+ .reject(&:even?)
219
+ .run_with(FiberStream::Sink.to_a)
220
+
221
+ result # => [1, 3]
222
+ ```
223
+
224
+ Use `filter_map` when filtering and transformation are one decision. Truthy
225
+ block results are emitted as transformed values; `false` and `nil` are
226
+ dropped:
227
+
228
+ ```ruby
229
+ ids =
230
+ FiberStream::Source.each([{ id: 1 }, {}, { id: 3 }])
231
+ .filter_map { |record| record[:id] }
232
+ .run_with(FiberStream::Sink.to_a)
233
+
234
+ ids # => [1, 3]
235
+ ```
236
+
237
+ Use `compact` to drop only `nil` while keeping `false`, and `map_concat` to
238
+ expand one upstream element into zero or more downstream elements:
239
+
240
+ ```ruby
241
+ tokens =
242
+ FiberStream::Source.each(["alpha beta", nil, "gamma"])
243
+ .compact
244
+ .map_concat { |line| line.split }
245
+ .run_with(FiberStream::Sink.to_a)
246
+
247
+ tokens # => ["alpha", "beta", "gamma"]
248
+ ```
249
+
250
+ Use `Flow.tap` for observation inside a reusable flow without changing the
251
+ element:
252
+
253
+ ```ruby
254
+ seen = []
255
+
256
+ observed =
257
+ FiberStream::Flow.tap { |value| seen << value }
258
+ .via(FiberStream::Flow.map { |value| value * 10 })
259
+
260
+ FiberStream::Source.each([1, 2])
261
+ .via(observed)
262
+ .run_with(FiberStream::Sink.to_a)
263
+ # => [10, 20]
264
+
265
+ seen # => [1, 2]
266
+ ```
267
+
219
268
  Use `parallel_map` for ordered scheduler-backed mapping when each element
220
269
  waits on non-blocking IO. It preserves input order while allowing up to
221
270
  `concurrency` mapping operations to be in flight:
@@ -240,6 +289,26 @@ profiles =
240
289
  profiles.map { |profile| profile.fetch(:id) } # => [1, 2, 3, 4]
241
290
  ```
242
291
 
292
+ Use `parallel_unordered_map` when every result can be handled independently
293
+ and lower head-of-line blocking matters more than input order. It still limits
294
+ in-flight mapping work to `concurrency`, but emits values as mapping jobs
295
+ finish:
296
+
297
+ ```ruby
298
+ require "async"
299
+ require "fiber_stream"
300
+
301
+ responses =
302
+ Sync do
303
+ FiberStream::Source.each(["/a", "/slow", "/b"])
304
+ .parallel_unordered_map(concurrency: 3) { |path| fetch_path(path) }
305
+ .run_with(FiberStream::Sink.to_a)
306
+ end
307
+
308
+ # Results are in completion order, not necessarily input order.
309
+ responses
310
+ ```
311
+
243
312
  Use `ractor_map` for ordered CPU-bound mapping in Ractor workers. The mapper
244
313
  must be shareable, usually by creating it with `Ractor.shareable_proc`.
245
314
 
@@ -284,6 +353,14 @@ FiberStream::Source.each([1, 2, 3])
284
353
  # => 6
285
354
  ```
286
355
 
356
+ Use `Sink.count` when only the number of elements matters:
357
+
358
+ ```ruby
359
+ FiberStream::Source.each([1, 2, 3])
360
+ .run_with(FiberStream::Sink.count)
361
+ # => 3
362
+ ```
363
+
287
364
  Use `Sink.foreach` when the terminal operation is a side effect and the stream
288
365
  values should not be accumulated:
289
366
 
@@ -384,6 +461,17 @@ batches =
384
461
  batches # => [[1, 2], [3, 4], [5]]
385
462
  ```
386
463
 
464
+ `Flow.scan` emits the updated accumulator for each upstream element:
465
+
466
+ ```ruby
467
+ running_totals =
468
+ FiberStream::Source.each([1, 2, 3, 4])
469
+ .scan(0) { |sum, number| sum + number }
470
+ .run_with(FiberStream::Sink.to_a)
471
+
472
+ running_totals # => [1, 3, 6, 10]
473
+ ```
474
+
387
475
  `Flow.take_while` emits the leading prefix while a predicate is truthy, then
388
476
  closes upstream at the first false or nil result:
389
477
 
@@ -450,14 +538,18 @@ merged =
450
538
 
451
539
  `merge` does not make scheduler-unaware blocking source work non-blocking and
452
540
  does not provide CPU parallelism. Use producer ractors with
453
- `Source.ractor_port` or `Source.ractor_merge_ports` when producer work needs
454
- true isolation.
455
-
456
- `Flow.buffer(count)` allows bounded prefetch. `Flow.async`, `Flow.buffer`,
457
- `Flow.parallel_map`, `Source.io`, `Source#merge`, `Sink.io`, and
458
- `Pipeline#run_async` require an installed `Fiber.scheduler` and a non-blocking
459
- current fiber when demanded or started. FiberStream does not install a
460
- scheduler and does not depend on Async at runtime.
541
+ `Source.ractor_producer` or `Source.ractor_merge_producers` when producer work
542
+ needs true isolation.
543
+
544
+ `Flow.buffer(count)` allows bounded prefetch. `Flow.throttle(rate:, per:)`
545
+ paces elements before downstream side effects. `Flow.async`, `Flow.buffer`,
546
+ `Flow.parallel_map`, `Flow.parallel_unordered_map`, `Source.io`,
547
+ `Source#merge`, `Sink.io`, and `Pipeline#run_async` require an installed
548
+ `Fiber.scheduler` and a non-blocking current fiber when demanded or started.
549
+ `Flow.throttle` requires that scheduler context only when it needs to wait.
550
+ Pass `throttle(limiter:)` with a `FiberStream::RateLimiter` when multiple
551
+ pipelines or repeated runs should share quota state. FiberStream does not
552
+ install a scheduler and does not depend on Async at runtime.
461
553
 
462
554
  ## API Surface
463
555
 
@@ -465,6 +557,8 @@ Sources:
465
557
 
466
558
  - `FiberStream::Source.each(enumerable)`
467
559
  - `FiberStream::Source.io(io, chunk_size: 16 * 1024, close: false)`
560
+ - `FiberStream::Source.ractor_producer(*args, transfer: :copy, ack_transfer: :copy) { |producer, *args| ... }`
561
+ - `FiberStream::Source.ractor_merge_producers(transfer: :copy, ack_transfer: :copy) { |group| ... }`
468
562
  - `FiberStream::Source.ractor_port(port, ack_port:, ack_transfer: :copy, cancel: true)`
469
563
  - `FiberStream::Source.ractor_merge_ports(ports, ack_transfer: :copy, cancel: true)`
470
564
 
@@ -475,16 +569,24 @@ Source convenience methods:
475
569
  - `Source#zip(source)`
476
570
  - `Source#merge(source)`
477
571
  - `Source#map { |element| ... }`
572
+ - `Source#filter_map { |element| ... }`
573
+ - `Source#compact`
574
+ - `Source#map_concat { |element| enumerable }`
478
575
  - `Source#parallel_map(concurrency:) { |element| ... }`
576
+ - `Source#parallel_unordered_map(concurrency:) { |element| ... }`
479
577
  - `Source#ractor_map(workers:, input_transfer: :copy, output_transfer: :copy) { |element| ... }`
480
578
  - `Source#select { |element| ... }`
579
+ - `Source#reject { |element| ... }`
481
580
  - `Source#take(count)`
482
581
  - `Source#drop(count)`
483
582
  - `Source#grouped(count)`
583
+ - `Source#scan(initial) { |accumulator, element| ... }`
484
584
  - `Source#take_while { |element| ... }`
485
585
  - `Source#drop_while { |element| ... }`
486
586
  - `Source#async`
487
587
  - `Source#buffer(count)`
588
+ - `Source#throttle(rate:, per: 1, burst: nil)`
589
+ - `Source#throttle(limiter:)`
488
590
  - `Source#lines(chomp: true, max_length: nil)`
489
591
  - `Source#split(separator, keep_separator: false, max_length: nil)`
490
592
  - `Source#to(sink)`
@@ -493,20 +595,31 @@ Source convenience methods:
493
595
  Flows:
494
596
 
495
597
  - `FiberStream::Flow.map { |element| ... }`
598
+ - `FiberStream::Flow.filter_map { |element| ... }`
599
+ - `FiberStream::Flow.compact`
600
+ - `FiberStream::Flow.map_concat { |element| enumerable }`
601
+ - `FiberStream::Flow.tap { |element| ... }`
496
602
  - `FiberStream::Flow.parallel_map(concurrency:) { |element| ... }`
603
+ - `FiberStream::Flow.parallel_unordered_map(concurrency:) { |element| ... }`
497
604
  - `FiberStream::Flow.ractor_map(workers:, input_transfer: :copy, output_transfer: :copy) { |element| ... }`
498
605
  - `FiberStream::Flow.select { |element| ... }`
606
+ - `FiberStream::Flow.reject { |element| ... }`
499
607
  - `FiberStream::Flow.take(count)`
500
608
  - `FiberStream::Flow.drop(count)`
501
609
  - `FiberStream::Flow.grouped(count)`
610
+ - `FiberStream::Flow.scan(initial) { |accumulator, element| ... }`
502
611
  - `FiberStream::Flow.take_while { |element| ... }`
503
612
  - `FiberStream::Flow.drop_while { |element| ... }`
504
613
  - `FiberStream::Flow.async`
505
614
  - `FiberStream::Flow.buffer(count)`
615
+ - `FiberStream::Flow.throttle(rate:, per: 1, burst: nil)`
616
+ - `FiberStream::Flow.throttle(limiter:)`
506
617
  - `FiberStream::Flow.lines(chomp: true, max_length: nil)`
507
618
  - `FiberStream::Flow.split(separator, keep_separator: false, max_length: nil)`
508
619
  - `Flow#via(flow)`
509
620
  - `Flow#to(sink)`
621
+ - `FiberStream::RateLimiter.new(rate:, per: 1, burst: nil)`
622
+ - `FiberStream::RateLimiter#acquire(permits: 1)`
510
623
 
511
624
  `lines` and `split` default to `max_length: nil`, which allows one
512
625
  unterminated line or frame to buffer without bound. Set a positive
@@ -516,6 +629,7 @@ Sinks:
516
629
 
517
630
  - `FiberStream::Sink.to_a`
518
631
  - `FiberStream::Sink.first`
632
+ - `FiberStream::Sink.count`
519
633
  - `FiberStream::Sink.fold(initial) { |accumulator, element| ... }`
520
634
  - `FiberStream::Sink.foreach { |element| ... }`
521
635
  - `FiberStream::Sink.io(io, close: false, flush: false)`
@@ -541,6 +655,7 @@ bundle exec ruby examples/file_copy.rb
541
655
  bundle exec ruby examples/backpressure_buffer.rb
542
656
  bundle exec ruby examples/background_execution.rb
543
657
  bundle exec ruby examples/ractor_map_hashing.rb
658
+ bundle exec ruby examples/ractor_producer_sources.rb
544
659
  bundle exec ruby examples/ractor_port_source.rb
545
660
  bundle exec ruby examples/ractor_merge_ports_and_map.rb
546
661
  bundle exec ruby examples/async_http_requests.rb
@@ -553,6 +668,9 @@ events so the difference between direct demand and bounded prefetch is visible.
553
668
  `examples/ractor_map_hashing.rb` demonstrates ordered Ractor-backed hashing
554
669
  with a shareable mapper proc and `input_transfer: :move`.
555
670
 
671
+ `examples/ractor_producer_sources.rb` demonstrates high-level owned producer
672
+ Ractors with `Source.ractor_producer` and `Source.ractor_merge_producers`.
673
+
556
674
  `examples/ractor_port_source.rb` demonstrates a producer Ractor that waits for
557
675
  `RactorPort::Ack` before sending each `RactorPort::Element`.
558
676
 
@@ -578,7 +696,7 @@ bundle exec ruby benchmarks/heavy_cpu_map.rb
578
696
 
579
697
  ## Development
580
698
 
581
- This project targets Ruby 4.x. The repository currently pins Ruby 4.0.3 in
699
+ This project targets Ruby 4.x. The repository currently pins Ruby 4.0.5 in
582
700
  `mise.toml`.
583
701
 
584
702
  Install dependencies:
data/examples/README.md CHANGED
@@ -11,6 +11,7 @@ bundle exec ruby examples/backpressure_buffer.rb
11
11
  bundle exec ruby examples/background_execution.rb
12
12
  bundle exec ruby examples/ractor_map_hashing.rb
13
13
  bundle exec ruby examples/ractor_port_source.rb
14
+ bundle exec ruby examples/ractor_producer_sources.rb
14
15
  bundle exec ruby examples/ractor_merge_ports_and_map.rb
15
16
  bundle exec ruby examples/async_http_requests.rb
16
17
  bundle exec ruby examples/async_http_streaming_body.rb
@@ -48,6 +49,11 @@ pipeline runs.
48
49
  `RactorPort::Ack`, and sends one typed `RactorPort::Element` per downstream
49
50
  demand.
50
51
 
52
+ `ractor_producer_sources.rb` demonstrates the high-level owned-producer APIs:
53
+ `Source.ractor_producer` for one producer and `Source.ractor_merge_producers`
54
+ for ready-order fan-in from multiple producers. FiberStream creates the ports,
55
+ producer Ractors, and cooperative cleanup path.
56
+
51
57
  `ractor_merge_ports_and_map.rb` runs CPU-bound work in multiple producer
52
58
  Ractors, merges their port outputs with `Source.ractor_merge_ports`, then runs
53
59
  another CPU-bound verification stage with `ractor_map`.
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
4
+
5
+ require "fiber_stream"
6
+
7
+ EMIT_NUMBERS =
8
+ Ractor.shareable_proc do |producer, range|
9
+ range.each do |number|
10
+ break unless producer.emit(number)
11
+ end
12
+ end
13
+
14
+ EMIT_TAGGED_NUMBERS =
15
+ Ractor.shareable_proc do |producer, tag, range|
16
+ range.each do |number|
17
+ break unless producer.emit([tag, number])
18
+ end
19
+ end
20
+
21
+ squares =
22
+ FiberStream::Source.ractor_producer(1..5, &EMIT_NUMBERS)
23
+ .map { |number| number * number }
24
+ .run_with(FiberStream::Sink.to_a)
25
+
26
+ puts "Squares from one FiberStream-owned producer Ractor:"
27
+ puts squares.join(", ")
28
+
29
+ merged =
30
+ FiberStream::Source.ractor_merge_producers do |group|
31
+ group.producer(:low, 1..3, &EMIT_TAGGED_NUMBERS)
32
+ group.producer(:high, 10..12, &EMIT_TAGGED_NUMBERS)
33
+ end.run_with(FiberStream::Sink.to_a)
34
+
35
+ puts
36
+ puts "Merged values from two owned producer Ractors:"
37
+ merged.each do |tag, number|
38
+ puts "- #{tag}: #{number}"
39
+ end
40
+
41
+ puts
42
+ puts "Producer blocks use RactorProducer#emit and stop when it returns false."
43
+ puts "FiberStream creates the data ports, ack ports, producer Ractors, and cleanup path."