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/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 = <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 = <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 = 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 0 : static List<Stream<T>> splitFrom<T>(Stream<T> stream, [int? count]) { 61 : count ??= 2; 62 0 : var splitter = StreamSplitter<T>(stream); 63 0 : var streams = List<Stream<T>>.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 0 : Stream<T> split() { 74 0 : if (_isClosed) { 75 0 : throw StateError("Can't call split() on a closed StreamSplitter."); 76 : } 77 : 78 0 : var controller = 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 0 : 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 0 : void _cancelSubscription() { 125 0 : assert(_controllers.isEmpty); 126 0 : assert(_isClosed); 127 : 128 : Future? future; 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 0 : 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 0 : 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 0 : 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 0 : 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 0 : void _onData(T data) { 186 0 : if (!_isClosed) _buffer.add(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 0 : void _onError(Object error, StackTrace stackTrace) { 194 0 : if (!_isClosed) _buffer.add(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 0 : void _onDone() { 202 0 : _isDone = true; 203 0 : for (var controller in _controllers) { 204 0 : _closeGroup.add(controller.close()); 205 : } 206 : } 207 : }