blob: 5db6300cdf864d5773920e7640df7fbceadaa443 [file] [log] [blame]
library change_detection;
typedef EvalExceptionHandler(error, stack);
/**
* An interface for [ChangeDetectorGroup] groups related watches together. It
* guarantees that within the group all watches will be reported in the order in
* which they were registered. It also provides an efficient way of removing the
* watch group.
*/
abstract class ChangeDetectorGroup<H> {
/**
* Watch a specific [field] on an [object].
*
* If the [field] is:
* - _name_ - Name of the property to watch. (If the [object] is a Map then
* treat the name as a key.)
* - _[]_ - Watch all items in an array.
* - _{}_ - Watch all items in a Map.
* - _._ - Watch the actual object identity.
*
*
* Parameters:
* - [object] to watch.
* - [field] to watch on the [object].
* - [handler] an opaque object passed on to [ChangeRecord].
*/
WatchRecord<H> watch(Object object, String field, H handler);
/** Use to remove all watches in the group in an efficient manner. */
void remove();
/** Create a child [ChangeDetectorGroup] */
ChangeDetectorGroup<H> newGroup();
}
/**
* An interface for [ChangeDetector]. An application can have multiple instances
* of the [ChangeDetector] to be used for checking different application domains.
*
* [ChangeDetector] works by comparing the identity of the objects not by
* calling the `.equals()` method. This is because ChangeDetector needs to have
* predictable performance, and the developer can implement `.equals()` on top
* of identity checks.
*
* - [H] A [ChangeRecord] has associated handler object. The handler object is
* opaque to the [ChangeDetector] but it is meaningful to the code which
* registered the watcher. It can be a data structure, an object, or a function.
* It is up to the developer to attach meaning to it.
*/
abstract class ChangeDetector<H> extends ChangeDetectorGroup<H> {
/**
* This method does the work of collecting the changes and returns them as a
* linked list of [ChangeRecord]s. The [ChangeRecord]s are returned in the
* same order as they were registered.
*/
ChangeRecord<H> collectChanges({ EvalExceptionHandler exceptionHandler,
AvgStopwatch stopwatch });
}
abstract class Record<H> {
/** The observed object. */
Object get object;
/**
* The field which is being watched:
* - _name_ - Name of the field to watch.
* - _[]_ - Watch all items in an array.
* - _{}_ - Watch all items in a Map.
* - _._ - Watch the actual object identity.
*/
String get field;
/**
* An application provided object which contains the specific logic which
* needs to be applied when the change is detected. The handler is opaque to
* the ChangeDetector and as such can be anything the application desires.
*/
H get handler;
/** Current value of the [field] on the [object] */
get currentValue;
/** Previous value of the [field] on the [object] */
get previousValue;
}
/**
* [WatchRecord] API which allows changing what object is being watched and
* manually triggering the checking.
*/
abstract class WatchRecord<H> extends Record<H> {
/** Set a new object for checking */
set object(value);
/**
* Check to see if the field on the object has changed. Returns [null] if no
* change, or a [ChangeRecord] if a change has been detected.
*/
ChangeRecord<H> check();
void remove();
}
/**
* Provides information about the changes which were detected in objects.
*
* It exposes a `nextChange` method for traversing all of the changes.
*/
abstract class ChangeRecord<H> extends Record<H> {
/** Next [ChangeRecord] */
ChangeRecord<H> get nextChange;
}
/**
* If the [ChangeDetector] is watching a [Map] then the [currentValue] of
* [Record] will contain an instance of this object. A [MapChangeRecord]
* contains the changes to the map since the last execution. The changes are
* reported as a list of [MapKeyValue]s which contain the key as well as its
* current and previous value.
*/
abstract class MapChangeRecord<K, V> {
/// The underlying iterable object
Map get map;
/// A list of [CollectionKeyValue]s which are in the iteration order. */
KeyValue<K, V> get mapHead;
/// A list of changed items.
ChangedKeyValue<K, V> get changesHead;
/// A list of new added items.
AddedKeyValue<K, V> get additionsHead;
/// A list of removed items
RemovedKeyValue<K, V> get removalsHead;
void forEachChange(void f(ChangedKeyValue<K, V> change));
void forEachAddition(void f(AddedKeyValue<K, V> addition));
void forEachRemoval(void f(RemovedKeyValue<K, V> removal));
}
/**
* Each item in map is wrapped in [MapKeyValue], which can track
* the [item]s [currentValue] and [previousValue] location.
*/
abstract class MapKeyValue<K, V> {
/// The item.
K get key;
/// Previous item location in the list or [null] if addition.
V get previousValue;
/// Current item location in the list or [null] if removal.
V get currentValue;
}
abstract class KeyValue<K, V> extends MapKeyValue<K, V> {
KeyValue<K, V> get nextKeyValue;
}
abstract class AddedKeyValue<K, V> extends MapKeyValue<K, V> {
AddedKeyValue<K, V> get nextAddedKeyValue;
}
abstract class RemovedKeyValue<K, V> extends MapKeyValue<K, V> {
RemovedKeyValue<K, V> get nextRemovedKeyValue;
}
abstract class ChangedKeyValue<K, V> extends MapKeyValue<K, V> {
ChangedKeyValue<K, V> get nextChangedKeyValue;
}
/**
* If the [ChangeDetector] is watching an [Iterable] then the [currentValue] of
* [Record] will contain this object. The [CollectionChangeRecord] contains the
* changes to the collection since the last execution. The changes are reported
* as a list of [CollectionChangeItem]s which contain the item as well as its
* current and previous position in the list.
*/
abstract class CollectionChangeRecord<V> {
/** The underlying iterable object */
Iterable get iterable;
/** A list of [CollectionItem]s which are in the iteration order. */
CollectionItem<V> get collectionHead;
/** A list of new [AddedItem]s. */
AddedItem<V> get additionsHead;
/** A list of [MovedItem]s. */
MovedItem<V> get movesHead;
/** A list of [RemovedItem]s. */
RemovedItem<V> get removalsHead;
void forEachAddition(void f(AddedItem<V> addition));
void forEachMove(void f(MovedItem<V> move));
void forEachRemoval(void f(RemovedItem<V> removal));
}
/**
* Each changed item in the collection is wrapped in a [CollectionChangeItem],
* which tracks the [item]s [currentKey] and [previousKey] location.
*/
abstract class CollectionChangeItem<V> {
/** Previous item location in the list or [null] if addition. */
int get previousIndex;
/** Current item location in the list or [null] if removal. */
int get currentIndex;
/** The item. */
V get item;
}
/**
* Used to create a linked list of collection items. These items are always in
* the iteration order of the collection.
*/
abstract class CollectionItem<V> extends CollectionChangeItem<V> {
CollectionItem<V> get nextCollectionItem;
}
/**
* A linked list of new items added to the collection. These items are always in
* the iteration order of the collection.
*/
abstract class AddedItem<V> extends CollectionChangeItem<V> {
AddedItem<V> get nextAddedItem;
}
/**
* A linked list of items moved in the collection. These items are always in
* the iteration order of the collection.
*/
abstract class MovedItem<V> extends CollectionChangeItem<V> {
MovedItem<V> get nextMovedItem;
}
/**
* A linked list of items removed from the collection. These items are always
* in the iteration order of the collection.
*/
abstract class RemovedItem<V> extends CollectionChangeItem<V> {
RemovedItem<V> get nextRemovedItem;
}
class AvgStopwatch extends Stopwatch {
int _count = 0;
int get count => _count;
void reset() {
_count = 0;
super.reset();
}
int increment(int count) => _count += count;
double get ratePerMs => elapsedMicroseconds == 0
? 0.0
: _count / elapsedMicroseconds * 1000;
}