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 : /// A collection of streams whose events are unified and sent through a central 8 : /// stream. 9 : /// 10 : /// Both errors and data events are forwarded through [stream]. The streams in 11 : /// the group won't be listened to until [stream] has a listener. **Note that 12 : /// this means that events emitted by broadcast streams will be dropped until 13 : /// [stream] has a listener.** 14 : /// 15 : /// If the `StreamGroup` is constructed using [new StreamGroup], [stream] will 16 : /// be single-subscription. In this case, if [stream] is paused or canceled, all 17 : /// streams in the group will likewise be paused or canceled, respectively. 18 : /// 19 : /// If the `StreamGroup` is constructed using [StreamGroup.broadcast], 20 : /// [stream] will be a broadcast stream. In this case, the streams in the group 21 : /// will never be paused and single-subscription streams in the group will never 22 : /// be canceled. **Note that single-subscription streams in a broadcast group 23 : /// may drop events if a listener is added and later removed.** Broadcast 24 : /// streams in the group will be canceled once [stream] has no listeners, and 25 : /// will be listened to again once [stream] has listeners. 26 : /// 27 : /// [stream] won't close until [close] is called on the group *and* every stream 28 : /// in the group closes. 29 : class StreamGroup<T> implements Sink<Stream<T>> { 30 : /// The stream through which all events from streams in the group are emitted. 31 0 : Stream<T> get stream => _controller.stream; 32 : late StreamController<T> _controller; 33 : 34 : /// Whether the group is closed, meaning that no more streams may be added. 35 : var _closed = false; 36 : 37 : /// The current state of the group. 38 : /// 39 : /// See [_StreamGroupState] for detailed descriptions of each state. 40 : var _state = _StreamGroupState.dormant; 41 : 42 : /// Streams that have been added to the group, and their subscriptions if they 43 : /// have been subscribed to. 44 : /// 45 : /// The subscriptions will be null until the group has a listener registered. 46 : /// If it's a broadcast group and it goes dormant again, broadcast stream 47 : /// subscriptions will be canceled and set to null again. Single-subscriber 48 : /// stream subscriptions will be left intact, since they can't be 49 : /// re-subscribed. 50 : final _subscriptions = <Stream<T>, StreamSubscription<T>?>{}; 51 : 52 : /// Merges the events from [streams] into a single single-subscription stream. 53 : /// 54 : /// This is equivalent to adding [streams] to a group, closing that group, and 55 : /// returning its stream. 56 0 : static Stream<T> merge<T>(Iterable<Stream<T>> streams) { 57 0 : var group = StreamGroup<T>(); 58 0 : streams.forEach(group.add); 59 0 : group.close(); 60 0 : return group.stream; 61 : } 62 : 63 : /// Merges the events from [streams] into a single broadcast stream. 64 : /// 65 : /// This is equivalent to adding [streams] to a broadcast group, closing that 66 : /// group, and returning its stream. 67 0 : static Stream<T> mergeBroadcast<T>(Iterable<Stream<T>> streams) { 68 0 : var group = StreamGroup<T>.broadcast(); 69 0 : streams.forEach(group.add); 70 0 : group.close(); 71 0 : return group.stream; 72 : } 73 : 74 : /// Creates a new stream group where [stream] is single-subscriber. 75 0 : StreamGroup() { 76 0 : _controller = StreamController<T>( 77 0 : onListen: _onListen, 78 0 : onPause: _onPause, 79 0 : onResume: _onResume, 80 0 : onCancel: _onCancel, 81 : sync: true); 82 : } 83 : 84 : /// Creates a new stream group where [stream] is a broadcast stream. 85 0 : StreamGroup.broadcast() { 86 0 : _controller = StreamController<T>.broadcast( 87 0 : onListen: _onListen, onCancel: _onCancelBroadcast, sync: true); 88 : } 89 : 90 : /// Adds [stream] as a member of this group. 91 : /// 92 : /// Any events from [stream] will be emitted through [this.stream]. If this 93 : /// group has a listener, [stream] will be listened to immediately; otherwise 94 : /// it will only be listened to once this group gets a listener. 95 : /// 96 : /// If this is a single-subscription group and its subscription has been 97 : /// canceled, [stream] will be canceled as soon as its added. If this returns 98 : /// a [Future], it will be returned from [add]. Otherwise, [add] returns 99 : /// `null`. 100 : /// 101 : /// Throws a [StateError] if this group is closed. 102 0 : @override 103 : Future? add(Stream<T> stream) { 104 0 : if (_closed) { 105 0 : throw StateError("Can't add a Stream to a closed StreamGroup."); 106 : } 107 : 108 0 : if (_state == _StreamGroupState.dormant) { 109 0 : _subscriptions.putIfAbsent(stream, () => null); 110 0 : } else if (_state == _StreamGroupState.canceled) { 111 : // Listen to the stream and cancel it immediately so that no one else can 112 : // listen, for consistency. If the stream has an onCancel listener this 113 : // will also fire that, which may help it clean up resources. 114 0 : return stream.listen(null).cancel(); 115 : } else { 116 0 : _subscriptions.putIfAbsent(stream, () => _listenToStream(stream)); 117 : } 118 : 119 : return null; 120 : } 121 : 122 : /// Removes [stream] as a member of this group. 123 : /// 124 : /// No further events from [stream] will be emitted through this group. If 125 : /// [stream] has been listened to, its subscription will be canceled. 126 : /// 127 : /// If [stream] has been listened to, this *synchronously* cancels its 128 : /// subscription. This means that any events from [stream] that haven't yet 129 : /// been emitted through this group will not be. 130 : /// 131 : /// If [stream]'s subscription is canceled, this returns 132 : /// [StreamSubscription.cancel]'s return value. Otherwise, it returns `null`. 133 0 : Future? remove(Stream<T> stream) { 134 0 : var subscription = _subscriptions.remove(stream); 135 0 : var future = subscription == null ? null : subscription.cancel(); 136 0 : if (_closed && _subscriptions.isEmpty) _controller.close(); 137 : return future; 138 : } 139 : 140 : /// A callback called when [stream] is listened to. 141 : /// 142 : /// This is called for both single-subscription and broadcast groups. 143 0 : void _onListen() { 144 0 : _state = _StreamGroupState.listening; 145 0 : _subscriptions.forEach((stream, subscription) { 146 : // If this is a broadcast group and this isn't the first time it's been 147 : // listened to, there may still be some subscriptions to 148 : // single-subscription streams. 149 : if (subscription != null) return; 150 0 : _subscriptions[stream] = _listenToStream(stream); 151 : }); 152 : } 153 : 154 : /// A callback called when [stream] is paused. 155 0 : void _onPause() { 156 0 : _state = _StreamGroupState.paused; 157 0 : for (var subscription in _subscriptions.values) { 158 0 : subscription!.pause(); 159 : } 160 : } 161 : 162 : /// A callback called when [stream] is resumed. 163 0 : void _onResume() { 164 0 : _state = _StreamGroupState.listening; 165 0 : for (var subscription in _subscriptions.values) { 166 0 : subscription!.resume(); 167 : } 168 : } 169 : 170 : /// A callback called when [stream] is canceled. 171 : /// 172 : /// This is only called for single-subscription groups. 173 0 : Future? _onCancel() { 174 0 : _state = _StreamGroupState.canceled; 175 : 176 0 : var futures = _subscriptions.values 177 0 : .map((subscription) => subscription!.cancel()) 178 0 : .toList(); 179 : 180 0 : _subscriptions.clear(); 181 0 : return futures.isEmpty ? null : Future.wait(futures); 182 : } 183 : 184 : /// A callback called when [stream]'s last listener is canceled. 185 : /// 186 : /// This is only called for broadcast groups. 187 0 : void _onCancelBroadcast() { 188 0 : _state = _StreamGroupState.dormant; 189 : 190 0 : _subscriptions.forEach((stream, subscription) { 191 : // Cancel the broadcast streams, since we can re-listen to those later, 192 : // but allow the single-subscription streams to keep firing. Their events 193 : // will still be added to [_controller], but then they'll be dropped since 194 : // it has no listeners. 195 0 : if (!stream.isBroadcast) return; 196 0 : subscription!.cancel(); 197 0 : _subscriptions[stream] = null; 198 : }); 199 : } 200 : 201 : /// Starts actively forwarding events from [stream] to [_controller]. 202 : /// 203 : /// This will pause the resulting subscription if [this] is paused. 204 0 : StreamSubscription<T> _listenToStream(Stream<T> stream) { 205 0 : var subscription = stream.listen(_controller.add, 206 0 : onError: _controller.addError, onDone: () => remove(stream)); 207 0 : if (_state == _StreamGroupState.paused) subscription.pause(); 208 : return subscription; 209 : } 210 : 211 : /// Closes the group, indicating that no more streams will be added. 212 : /// 213 : /// If there are no streams in the group, [stream] is closed immediately. 214 : /// Otherwise, [stream] will close once all streams in the group close. 215 : /// 216 : /// Returns a [Future] that completes once [stream] has actually been closed. 217 0 : @override 218 : Future close() { 219 0 : if (_closed) return _controller.done; 220 : 221 0 : _closed = true; 222 0 : if (_subscriptions.isEmpty) _controller.close(); 223 : 224 0 : return _controller.done; 225 : } 226 : } 227 : 228 : /// An enum of possible states of a [StreamGroup]. 229 : class _StreamGroupState { 230 : /// The group has no listeners. 231 : /// 232 : /// New streams added to the group will be listened once the group has a 233 : /// listener. 234 : static const dormant = _StreamGroupState('dormant'); 235 : 236 : /// The group has one or more listeners and is actively firing events. 237 : /// 238 : /// New streams added to the group will be immediately listeners. 239 : static const listening = _StreamGroupState('listening'); 240 : 241 : /// The group is paused and no more events will be fired until it resumes. 242 : /// 243 : /// New streams added to the group will be listened to, but then paused. They 244 : /// will be resumed once the group itself is resumed. 245 : /// 246 : /// This state is only used by single-subscriber groups. 247 : static const paused = _StreamGroupState('paused'); 248 : 249 : /// The group is canceled and no more events will be fired ever. 250 : /// 251 : /// New streams added to the group will be listened to, canceled, and 252 : /// discarded. 253 : /// 254 : /// This state is only used by single-subscriber groups. 255 : static const canceled = _StreamGroupState('canceled'); 256 : 257 : /// The name of the state. 258 : /// 259 : /// Used for debugging. 260 : final String name; 261 : 262 11 : const _StreamGroupState(this.name); 263 : 264 0 : @override 265 0 : String toString() => name; 266 : }