blob: b7ca0da6ee9b59636a473a3594590ef71fc8e479 [file] [log] [blame]
// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async' show StreamController, scheduleMicrotask;
import 'dart:collection' show UnmodifiableListView;
import '../../../protobuf.dart' show GeneratedMessage, FieldInfo, EventPlugin;
/// Provides a stream of changes to fields in a [GeneratedMessage].
/// (Experimental.)
///
/// This mixin is enabled via an option in dart_options.proto in
/// dart-protoc-plugin.
mixin PbEventMixin {
final eventPlugin = EventBuffer();
/// A stream of changes to fields in the GeneratedMessage.
///
/// Events are buffered and delivered via a microtask or in
/// the next call to [deliverChanges], whichever happens first.
Stream<List<PbFieldChange>> get changes => eventPlugin.changes;
/// Delivers buffered field change events synchronously,
/// instead of waiting for the microtask to run.
void deliverChanges() => eventPlugin.deliverChanges();
}
/// A change to a field in a [GeneratedMessage].
class PbFieldChange {
final GeneratedMessage? message;
final FieldInfo info;
final Object? oldValue;
final Object? newValue;
PbFieldChange(this.message, this.info, this.oldValue, this.newValue);
int get tag => info.tagNumber;
}
/// A buffering implementation of event delivery.
/// (Loosely based on package:observe's ChangeNotifier.)
class EventBuffer extends EventPlugin {
// An EventBuffer is created for each GeneratedMessage, so
// initialization should be fast; create fields lazily.
GeneratedMessage? _parent;
StreamController<List<PbFieldChange>>? _controller;
// If _buffer is non-null, at least one event is in the buffer
// and a microtask has been scheduled to empty it.
List<PbFieldChange>? _buffer;
@override
void attach(GeneratedMessage parent) {
assert(_parent == null);
ArgumentError.checkNotNull(parent, 'parent');
_parent = parent;
}
Stream<List<PbFieldChange>> get changes {
final controller = _controller ??= StreamController.broadcast(sync: true);
return controller.stream;
}
@override
bool get hasObservers => _controller != null && _controller!.hasListener;
void deliverChanges() {
final records = _buffer;
_buffer = null;
if (records != null && hasObservers) {
_controller!.add(UnmodifiableListView<PbFieldChange>(records));
}
}
void addEvent(PbFieldChange change) {
if (!hasObservers) return;
if (_buffer == null) {
_buffer = <PbFieldChange>[];
scheduleMicrotask(deliverChanges);
}
_buffer!.add(change);
}
@override
void beforeSetField(FieldInfo fi, Object? newValue) {
var oldValue = _parent!.getFieldOrNull(fi.tagNumber);
oldValue ??= fi.readonlyDefault;
if (identical(oldValue, newValue)) return;
addEvent(PbFieldChange(_parent, fi, oldValue, newValue));
}
@override
void beforeClearField(FieldInfo fi) {
final oldValue = _parent!.getFieldOrNull(fi.tagNumber);
if (oldValue == null) return;
final newValue = fi.readonlyDefault;
addEvent(PbFieldChange(_parent, fi, oldValue, newValue));
}
}