Chromium Code Reviews| Index: sdk/lib/async/stream_controller.dart |
| diff --git a/sdk/lib/async/stream_controller.dart b/sdk/lib/async/stream_controller.dart |
| index 3412e48f459536d843eafefe0d3a1b02d36147d9..268af10757e402542ecbca9694f6e0c3e171b512 100644 |
| --- a/sdk/lib/async/stream_controller.dart |
| +++ b/sdk/lib/async/stream_controller.dart |
| @@ -73,16 +73,26 @@ abstract class StreamController<T> implements EventSink<T> { |
| => new _StreamControllerImpl<T>(onListen, onPause, onResume, onCancel); |
| /** |
| - * A controller where [stream] creates new stream each time it is read. |
| + * A controller where [stream] can be listened to more than once. |
| * |
| - * The controller distributes any events to all currently subscribed streams. |
| + * The [Stream] returned by [stream] is a broadcast stream. It can be listened |
| + * to more than once. |
| + * |
| + * The controller distributes any events to all currently subscribed |
| + * listeners. |
| + * It is not allowed to call [add], [addError], or [close] before a previous |
| + * call has returned. |
| + * |
| + * Each listener is handled independently, |
|
floitsch
2013/05/28 11:47:36
merge lines.
Lasse Reichstein Nielsen
2013/05/28 12:06:04
It was actually split deliberatly.
If displayed as
|
| + * and if they pause, only the pausing listener is affected. |
| + * A paused listener will buffer events internally until unpaused or canceled. |
| * |
| * The [onListen] callback is called when the first listener is subscribed, |
| - * and the [onCancel] is called when there is no longer any active listeners. |
| + * and the [onCancel] is called when there are no longer any active listeners. |
| * If a listener is added again later, after the [onCancel] was called, |
| * the [onListen] will be called again. |
| */ |
| - factory StreamController.multiplex({void onListen(), void onCancel()}) { |
| + factory StreamController.broadcast({void onListen(), void onCancel()}) { |
| return new _MultiplexStreamController<T>(onListen, onCancel); |
| } |
| @@ -318,28 +328,102 @@ class _ControllerSubscription<T> extends _BufferingStreamSubscription<T> { |
| } |
| } |
| +class _MultiplexStream<T> extends _StreamImpl<T> { |
| + _MultiplexStreamController _controller; |
| + |
| + _MultiplexStream(this._controller); |
| + |
| + bool get isBroadcast => true; |
| + |
| + StreamSubscription<T> _createSubscription( |
| + void onData(T data), |
| + void onError(Object error), |
| + void onDone(), |
| + bool cancelOnError) { |
| + return new _MultiplexSubscription<T>( |
| + _controller, onData, onError, onDone, cancelOnError); |
| + } |
| + |
| + void _onListen(_BufferingStreamSubscription subscription) { |
| + _controller._recordListen(subscription); |
| + } |
| +} |
| + |
| +abstract class _MultiplexSubscriptionLink { |
| + _MultiplexSubscriptionLink _next; |
| + _MultiplexSubscriptionLink _previous; |
| +} |
| + |
| +class _MultiplexSubscription<T> extends _ControllerSubscription<T> |
| + implements _MultiplexSubscriptionLink { |
| + static const int _STATE_EVENT_ID = 1; |
| + static const int _STATE_FIRING = 2; |
| + static const int _STATE_REMOVE_AFTER_FIRING = 4; |
| + int _eventState; |
| + |
| + _MultiplexSubscriptionLink _next; |
| + _MultiplexSubscriptionLink _previous; |
| + |
| + _MultiplexSubscription(_StreamControllerLifecycle controller, |
| + void onData(T data), |
| + void onError(Object error), |
| + void onDone(), |
| + bool cancelOnError) |
| + : super(controller, onData, onError, onDone, cancelOnError) { |
| + _next = _previous = this; |
| + } |
| + |
| + _MultiplexStreamController get _controller => super._controller; |
| + |
| + bool _expectsEvent(int eventId) { |
| + return (_eventState & _STATE_EVENT_ID) == eventId; |
| + } |
| + |
| + void _toggleEventId() { |
| + _eventState ^= _STATE_EVENT_ID; |
| + } |
| + |
| + bool get _isFiring => (_eventState & _STATE_FIRING) != 0; |
| + |
| + bool _setRemoveAfterFiring() { |
| + assert(_isFiring); |
| + _eventState |= _STATE_REMOVE_AFTER_FIRING; |
| + } |
| + |
| + bool get _removeAfterFiring => |
| + (_eventState & _STATE_REMOVE_AFTER_FIRING) != 0; |
| +} |
| + |
| + |
| class _MultiplexStreamController<T> implements StreamController<T>, |
| - _StreamControllerLifecycle<T> { |
| + _StreamControllerLifecycle<T>, |
| + _MultiplexSubscriptionLink { |
| + static const int _STATE_INITIAL = 0; |
| + static const int _STATE_EVENT_ID = 1; |
| + static const int _STATE_FIRING = 2; |
| + static const int _STATE_CLOSED = 4; |
| + |
| final _NotificationHandler _onListen; |
| final _NotificationHandler _onCancel; |
| - /** Set when the [close] method is called. */ |
| - bool _isClosed = false; |
| - // TODO(lrn): Make a more efficient implementation of these subscriptions, |
| - // e.g., the traditional double-linked list with concurrent add and remove |
| - // while firing. |
| - Set<_BufferingStreamSubscription<T>> _streams; |
| + // State of the controller. |
| + int _state = _STATE_INITIAL; |
| - _MultiplexStreamController(this._onListen, this._onCancel) |
| - : _streams = new Set<_BufferingStreamSubscription<T>>(); |
| + // Double-linked list of active listeners. |
| + _MultiplexSubscriptionLink _next; |
| + _MultiplexSubscriptionLink _previous; |
| + |
| + _MultiplexStreamController(this._onListen, this._onCancel) { |
| + _next = _previous = this; |
| + } |
| // StreamController interface. |
| - Stream<T> get stream => new _ControllerStream<T>(this); |
| + Stream<T> get stream => new _MultiplexStream<T>(this); |
| EventSink<T> get sink => new _EventSinkView<T>(this); |
| - bool get isClosed => _isClosed; |
| + bool get isClosed => (_state & _STATE_CLOSED) != 0; |
| /** |
| * A multiplex controller is never paused. |
| @@ -350,22 +434,50 @@ class _MultiplexStreamController<T> implements StreamController<T>, |
| bool get isPaused => false; |
| /** Whether there are currently a subscriber on the [Stream]. */ |
| - bool get hasListener => !_streams.isEmpty; |
| + bool get hasListener => !_isEmpty; |
| + |
| + // Linked list helpers |
| + |
| + bool get _isEmpty => identical(_next, this); |
| + |
| + /** Adds subscription to linked list of active listeners. */ |
| + void _addListener(_MultiplexSubscription<T> subscription) { |
| + _MultiplexSubscriptionLink previous = _previous; |
| + previous._next = subscription; |
| + _previous = subscription._previous; |
| + subscription._previous._next = this; |
| + subscription._previous = previous; |
| + subscription._eventState = (_state & _STATE_EVENT_ID); |
| + } |
| + |
| + void _removeListener(_MultiplexSubscription<T> subscription) { |
| + assert(identical(subscription._controller, this)); |
| + assert(!identical(subscription._next, subscription)); |
| + subscription._previous._next = subscription._next; |
| + subscription._next._previous = subscription._previous; |
| + subscription._next = subscription._previous = subscription; |
| + } |
| // _StreamControllerLifecycle interface. |
| - void _recordListen(_BufferingStreamSubscription<T> subscription) { |
| - bool isFirst = _streams.isEmpty; |
| - _streams.add(subscription); |
| - if (isFirst) { |
| + void _recordListen(_MultiplexSubscription<T> subscription) { |
| + _addListener(subscription); |
| + if (identical(_next, _previous)) { |
| + // Only one listener, so it must be the first listener. |
| _runGuarded(_onListen); |
| } |
| } |
| - void _recordCancel(_BufferingStreamSubscription<T> subscription) { |
| - _streams.remove(subscription); |
| - if (_streams.isEmpty) { |
| - _runGuarded(_onCancel); |
| + void _recordCancel(_MultiplexSubscription<T> subscription) { |
| + if (subscription._isFiring) { |
| + subscription._setRemoveAfterFiring(); |
| + } else { |
| + _removeListener(subscription); |
| + // If we are firing an event, we will check for there being no listeners |
|
floitsch
2013/05/28 11:47:36
If we are currently firing an event we don't do th
Lasse Reichstein Nielsen
2013/05/28 12:06:04
rewritten.
|
| + // left after we stop firing. |
| + if ((_state & _STATE_FIRING) == 0 && _isEmpty) { |
| + _runGuarded(_onCancel); |
| + } |
| } |
| } |
| @@ -375,36 +487,69 @@ class _MultiplexStreamController<T> implements StreamController<T>, |
| // EventSink interface. |
| void add(T data) { |
| - if (_streams.isEmpty) return; |
| + assert(!isClosed); |
| + if (_isEmpty) return; |
| _forEachListener((_BufferingStreamSubscription<T> subscription) { |
| subscription._add(data); |
| }); |
| } |
| void addError(Object error, [Object stackTrace]) { |
| - if (_streams.isEmpty) return; |
| + assert(!isClosed); |
| + if (_isEmpty) return; |
| _forEachListener((_BufferingStreamSubscription<T> subscription) { |
| subscription._addError(error); |
| }); |
| } |
| void close() { |
| - _isClosed = true; |
| - if (_streams.isEmpty) return; |
| - _forEachListener((_BufferingStreamSubscription<T> subscription) { |
| - _streams.remove(subscription); |
| + assert(!isClosed); |
| + _state |= _STATE_CLOSED; |
| + if (_isEmpty) return; |
| + _forEachListener((_MultiplexSubscription<T> subscription) { |
| subscription._close(); |
| + subscription._eventState |= |
| + _MultiplexSubscription._STATE_REMOVE_AFTER_FIRING; |
| }); |
| } |
| void _forEachListener( |
| void action(_BufferingStreamSubscription<T> subscription)) { |
| - List<_BufferingStreamSubscription<T>> subscriptions = _streams.toList(); |
| - for (_BufferingStreamSubscription<T> subscription in subscriptions) { |
| - if (_streams.contains(subscription)) { |
| + if ((_state & _STATE_FIRING) != 0) { |
| + throw new StateError( |
| + "Cannot fire new event. Controller is already firing an event"); |
| + } |
| + if (_isEmpty) return; |
| + |
| + // Get event id of this event. |
| + int id = (_state & _STATE_EVENT_ID); |
| + // Start firing (set the _STATE_FIRING bit). We don't do [_onCancel] |
| + // callbacks while firing, and we prevent reentrancy of this function. |
| + // |
| + // Set [_state]'s event id to the next event's id. |
| + // Any listeners added while firing this event will expect the next event, |
| + // not this one, and won't get notified. |
| + _state ^= _STATE_EVENT_ID | _STATE_FIRING; |
| + _MultiplexSubscriptionLink link = _next; |
| + while (!identical(link, this)) { |
| + _MultiplexSubscription<T> subscription = link; |
| + if (subscription._expectsEvent(id)) { |
| + subscription._eventState |= _MultiplexSubscription._STATE_FIRING; |
| action(subscription); |
| + subscription._toggleEventId(); |
| + link = subscription._next; |
| + if (subscription._removeAfterFiring) { |
| + _removeListener(subscription); |
| + } |
| + subscription._eventState &= ~_MultiplexSubscription._STATE_FIRING; |
| + } else { |
| + link = subscription._next; |
| } |
| } |
| + _state &= ~_STATE_FIRING; |
| + |
| + if (_isEmpty) { |
| + _runGuarded(_onCancel); |
| + } |
| } |
| } |
| - |