Line data Source code
1 : // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 : // for details. All rights reserved. Use of this source code is governed by a
3 : // BSD-style license that can be found in the LICENSE file.
4 :
5 : import 'dart:async';
6 :
7 : import 'future_group.dart';
8 : import 'result.dart';
9 :
10 : /// A class that splits a single source stream into an arbitrary number of
11 : /// (single-subscription) streams (called "branch") that emit the same events.
12 : ///
13 : /// Each branch will emit all the same values and errors as the source stream,
14 : /// regardless of which values have been emitted on other branches. This means
15 : /// that the splitter stores every event that has been emitted so far, which may
16 : /// consume a lot of memory. The user can call [close] to indicate that no more
17 : /// branches will be created, and this memory will be released.
18 : ///
19 : /// The source stream is only listened to once a branch is created *and listened
20 : /// to*. It's paused when all branches are paused *or when all branches are
21 : /// canceled*, and resumed once there's at least one branch that's listening and
22 : /// unpaused. It's not canceled unless no branches are listening and [close] has
23 : /// been called.
24 : class StreamSplitter<T> {
25 : /// The wrapped stream.
26 : final Stream<T> _stream;
27 :
28 : /// The subscription to [_stream].
29 : ///
30 : /// This will be `null` until a branch has a listener.
31 : StreamSubscription<T> _subscription;
32 :
33 : /// The buffer of events or errors that have already been emitted by
34 : /// [_stream].
35 : final _buffer = new List<Result<T>>();
36 :
37 : /// The controllers for branches that are listening for future events from
38 : /// [_stream].
39 : ///
40 : /// Once a branch is canceled, it's removed from this list. When [_stream] is
41 : /// done, all branches are removed.
42 : final _controllers = new Set<StreamController<T>>();
43 :
44 : /// A group of futures returned by [close].
45 : ///
46 : /// This is used to ensure that [close] doesn't complete until all
47 : /// [StreamController.close] and [StreamSubscription.cancel] calls complete.
48 : final _closeGroup = new FutureGroup();
49 :
50 : /// Whether [_stream] is done emitting events.
51 : var _isDone = false;
52 :
53 : /// Whether [close] has been called.
54 : var _isClosed = false;
55 :
56 : /// Splits [stream] into [count] identical streams.
57 : ///
58 : /// [count] defaults to 2. This is the same as creating [count] branches and
59 : /// then closing the [StreamSplitter].
60 : static List<Stream<T>> splitFrom<T>(Stream<T> stream, [int count]) {
61 : if (count == null) count = 2;
62 0 : var splitter = new StreamSplitter<T>(stream);
63 0 : var streams = new List<Stream>.generate(count, (_) => splitter.split());
64 0 : splitter.close();
65 : return streams;
66 : }
67 :
68 0 : StreamSplitter(this._stream);
69 :
70 : /// Returns a single-subscription stream that's a copy of the input stream.
71 : ///
72 : /// This will throw a [StateError] if [close] has been called.
73 : Stream<T> split() {
74 0 : if (_isClosed) {
75 0 : throw new StateError("Can't call split() on a closed StreamSplitter.");
76 : }
77 :
78 0 : var controller = new StreamController<T>(
79 0 : onListen: _onListen, onPause: _onPause, onResume: _onResume);
80 0 : controller.onCancel = () => _onCancel(controller);
81 :
82 0 : for (var result in _buffer) {
83 0 : result.addTo(controller);
84 : }
85 :
86 0 : if (_isDone) {
87 0 : _closeGroup.add(controller.close());
88 : } else {
89 0 : _controllers.add(controller);
90 : }
91 :
92 0 : return controller.stream;
93 : }
94 :
95 : /// Indicates that no more branches will be requested via [split].
96 : ///
97 : /// This clears the internal buffer of events. If there are no branches or all
98 : /// branches have been canceled, this cancels the subscription to the input
99 : /// stream.
100 : ///
101 : /// Returns a [Future] that completes once all events have been processed by
102 : /// all branches and (if applicable) the subscription to the input stream has
103 : /// been canceled.
104 : Future close() {
105 0 : if (_isClosed) return _closeGroup.future;
106 0 : _isClosed = true;
107 :
108 0 : _buffer.clear();
109 0 : if (_controllers.isEmpty) _cancelSubscription();
110 :
111 0 : return _closeGroup.future;
112 : }
113 :
114 : /// Cancel [_subscription] and close [_closeGroup].
115 : ///
116 : /// This should be called after all the branches' subscriptions have been
117 : /// canceled and the splitter has been closed. In that case, we won't use the
118 : /// events from [_subscription] any more, since there's nothing to pipe them
119 : /// to and no more branches will be created. If [_subscription] is done,
120 : /// canceling it will be a no-op.
121 : ///
122 : /// This may also be called before any branches have been created, in which
123 : /// case [_subscription] will be `null`.
124 : void _cancelSubscription() {
125 : assert(_controllers.isEmpty);
126 : assert(_isClosed);
127 :
128 : var future = null;
129 0 : if (_subscription != null) future = _subscription.cancel();
130 0 : if (future != null) _closeGroup.add(future);
131 0 : _closeGroup.close();
132 : }
133 :
134 : // StreamController events
135 :
136 : /// Subscribe to [_stream] if we haven't yet done so, and resume the
137 : /// subscription if we have.
138 : void _onListen() {
139 0 : if (_isDone) return;
140 :
141 0 : if (_subscription != null) {
142 : // Resume the subscription in case it was paused, either because all the
143 : // controllers were paused or because the last one was canceled. If it
144 : // wasn't paused, this will be a no-op.
145 0 : _subscription.resume();
146 : } else {
147 0 : _subscription =
148 0 : _stream.listen(_onData, onError: _onError, onDone: _onDone);
149 : }
150 : }
151 :
152 : /// Pauses [_subscription] if every controller is paused.
153 : void _onPause() {
154 0 : if (!_controllers.every((controller) => controller.isPaused)) return;
155 0 : _subscription.pause();
156 : }
157 :
158 : /// Resumes [_subscription].
159 : ///
160 : /// If [_subscription] wasn't paused, this is a no-op.
161 : void _onResume() {
162 0 : _subscription.resume();
163 : }
164 :
165 : /// Removes [controller] from [_controllers] and cancels or pauses
166 : /// [_subscription] as appropriate.
167 : ///
168 : /// Since the controller emitting a done event will cause it to register as
169 : /// canceled, this is the only way that a controller is ever removed from
170 : /// [_controllers].
171 : void _onCancel(StreamController controller) {
172 0 : _controllers.remove(controller);
173 0 : if (_controllers.isNotEmpty) return;
174 :
175 0 : if (_isClosed) {
176 0 : _cancelSubscription();
177 : } else {
178 0 : _subscription.pause();
179 : }
180 : }
181 :
182 : // Stream events
183 :
184 : /// Buffers [data] and passes it to [_controllers].
185 : void _onData(T data) {
186 0 : if (!_isClosed) _buffer.add(new Result.value(data));
187 0 : for (var controller in _controllers) {
188 0 : controller.add(data);
189 : }
190 : }
191 :
192 : /// Buffers [error] and passes it to [_controllers].
193 : void _onError(Object error, StackTrace stackTrace) {
194 0 : if (!_isClosed) _buffer.add(new Result.error(error, stackTrace));
195 0 : for (var controller in _controllers) {
196 0 : controller.addError(error, stackTrace);
197 : }
198 : }
199 :
200 : /// Marks [_controllers] as done.
201 : void _onDone() {
202 0 : _isDone = true;
203 0 : for (var controller in _controllers) {
204 0 : _closeGroup.add(controller.close());
205 : }
206 : }
207 : }
|