blob: d0cc5c5bf1cddaa70777087d5410e85cd7b9194e [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. 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';
import 'dart:collection';
import 'dart:typed_data';
import '../change.dart';
import '../leaf_value.dart';
import '../value_observer.dart';
import 'converted_change.dart';
import 'converter.dart';
import 'key_value_storage.dart';
import 'ordered_list_tree_path.dart';
/// Implementation of Ordered List CRDT.
///
/// The ordered List CRDT can be changed concurrently by multiple instances, each
/// associated with an instance ID. Changes from other instances are applied when
/// calling [applyChange], while changes from this instance can be retrieved by
/// calling [getChange].
//
// The contents of the list are stored using a KeyValueStorage. The values of
// the KeyValueStorage are the elements of the list, while the keys are created
// in such a way that their lexicographic order corresponds to the order of the
// elements of the list.
//
// Key generation is based on an implicit tree structure, where edges are
// directed from parent nodes towards child nodes. For each edge (v, u) there
// is an associated bytelist f(v, u). For each node v there is an associated
// bytelist F(v), which is the concatenation of bytelists associated with the
// edges on path from root to v.
//
// Example:
//
// root -----> a -----> b -----> v
// |
// -------> u
//
// F(v) = f(root, a) f(a, b) f(b, v)
// F(u) = f(root, a) f(a, u)
//
// The tree has nodes of two types:
// Value nodes, for which:
// f(p, v) = {1}{timestamp}
//
// Implicit nodes, for which:
// f(p, v) = {0|2}{instanceId}
// The prefix of f(p, v) is 0 if (v) is a left child of (p) or 2 if it is a
// right child.
//
// Value nodes correspond to leaves, while implicit nodes are the internal
// ones. There is a one-to-one correspondence between the elements of the list
// and the set of value nodes. Every value node has an implicit node as a
// parent that is created on the same instance as that value node.
//
// Every implicit node has exactly one value child node. It might also have one
// left and one right (implicit) nodes per instance.
//
// Insertion:
// In the most common case, a new element is inserted between two existing
// ones. Let (v) be the new value node, and (left) and (right) be the nodes
// of the previous and next elements correspondingly. Also, let (pleft) and
// (pright) be their corresponding parents, both of them being, by
// definition, implicit nodes.
//
// If (pleft) is not an ancestor of (pright), then we add a new implicit node
// (par_v) as a right child of (pleft) and (v) as the value node of (par_v):
//
// root -----> pleft -----> left
// | |
// | - - -> par(v) - - -> v
// |
// -----> pright ----> right
//
// We have now defined:
//
// F(v) = F(pleft){2}{instanceId}{1}{timestamp}
//
// The invariant of the keys being ordered in lexicographic order is maintained as:
// 1. F(left) = F(pleft){1}{timestamp}. So F(left) < F(v).
// 2. F(right) > F(pleft), and F(pleft) is not a prefix of F(right).
// So F(right) > F(v).
//
// Otherwise, i.e. when (pleft) is an ancestor of (pright), we insert a new
// implicit node (par_v) as the left child of (pleft) and (v) as the value of
// that node:
//
// root -----> pleft -----> left
// |
// | - - -> par(v) - - -> v
// | |
// ------> pright ----> right
//
// We have now defined:
//
// F(v) = F(pright){0}{instanceId}{1}{timestamp}
//
// The invariant of the lexicographic order of the keys is still maintained since:
// 1. F(right) = F(pright){1}{timestamp}. So F(right) > F(v).
// 2. F(left) < F(right), and F(left) can't start with F(pright),
// so F(left) < F(pright) < F(v).
//
//
// instanceId:
// instanceIds are introduced to handle concurrent insertions on different
// instances. Last instanceId in the path to the node should correspond to
// the instance that created that node.
//
// timestamp:
// timestamps are introduced to avoid tombstones. If some key is deleted,
// then it should never be inserted again. In other case conflict might
// happen. The same key may be inserted only on the same instance, so
// incremental timer per instance is applicable.
//
// TODO:
// Now each implicit node have only one value child. It should be changed. And
// at the same time value nodes should get able to have implicit node as a
// child. And left/right edges should have no id written on them.
//
// TODO: handle null values in CRDT methods.
// ignore: private_collision_in_mixin_application
class OrderedListValue<E> extends ListBase<E> implements LeafValue {
//with ListMixin<E> {
final KeyValueStorage<OrderedListTreePath, E> _storage;
final Uint8List _instanceId;
int _incrementalTime = 0;
final OrderedListTreePath _root = OrderedListTreePath.root();
final StreamController<OrderedListChange<E>> _changeController =
StreamController<OrderedListChange<E>>.broadcast();
final MapToKVListConverter _converter;
final bool Function(E, E) _equals;
/// Default constructor.
OrderedListValue(this._instanceId, {bool equals(E element1, E element2)})
: _converter = MapToKVListConverter<OrderedListTreePath, E>(
keyConverter: orderedListTreePathConverter),
_storage = KeyValueStorage<OrderedListTreePath, E>(),
_equals = equals ?? ((E element1, E element2) => element1 == element2);
@override
Stream<OrderedListChange<E>> get onChange => _changeController.stream;
@override
void insert(int index, E element) {
final sortedKeys = _sortedKeysList();
if (index < 0 || index > sortedKeys.length) {
throw RangeError.value(index, 'index', 'Index is out of range.');
}
OrderedListTreePath newKey;
if (sortedKeys.isEmpty) {
newKey = _root.getChild(ChildType.value, _instanceId, _timestamp);
} else {
bool becomeRightChild = (index != 0 &&
(index == sortedKeys.length ||
!sortedKeys[index].isDescendant(sortedKeys[index - 1])));
if (becomeRightChild) {
newKey = sortedKeys[index - 1]
.getChild(ChildType.right, _instanceId, _timestamp);
} else {
newKey =
sortedKeys[index].getChild(ChildType.left, _instanceId, _timestamp);
}
}
_storage[newKey] = element;
}
@override
void insertAll(int index, Iterable<E> iterable) {
if (index < 0 || index > length) {
throw RangeError.value(index, 'index', 'Index is out of range.');
}
int insertIndex = index;
for (final element in iterable) {
insert(insertIndex++, element);
}
}
@override
int indexOf(Object element, [int start = 0]) {
final values = _valuesList();
for (int i = start; i < values.length; i++) {
if (_equals(values[i], element)) {
return i;
}
}
return -1;
}
@override
int lastIndexOf(Object element, [int start]) {
final values = _valuesList();
start ??= values.length - 1;
for (int i = start; i >= 0; i--) {
if (_equals(values[i], element)) {
return i;
}
}
return -1;
}
@override
E removeAt(int index) {
return _storage.remove(_sortedKeysList()[index]);
}
@override
bool remove(Object element) {
final keys = _sortedKeysList();
final values = _valuesList();
for (int i = 0; i < values.length; i++) {
if (_equals(values[i], element)) {
_storage.remove(keys[i]);
return true;
}
}
return false;
}
@override
E removeLast() => removeAt(length - 1);
@override
void removeRange(int start, int end) {
_sortedKeysList().getRange(start, end).forEach(_storage.remove);
}
@override
void removeWhere(bool test(E element)) {
final keys = _sortedKeysList();
final values = _valuesList();
for (int i = 0; i < values.length; i++) {
if (test(values[i])) {
_storage.remove(keys[i]);
}
}
}
@override
void replaceRange(int start, int end, Iterable<E> replacement) {
removeRange(start, end);
insertAll(start, replacement);
}
@override
void retainWhere(bool test(E element)) {
removeWhere((element) => !test(element));
}
@override
E operator [](int index) {
return _valuesList()[index];
}
@override
void operator []=(int index, E element) {
removeAt(index);
insert(index, element);
}
@override
int get length => _storage.length;
@override
set length(int newLength) {
// This list is not fixed length.
// However, it does not support the length setter because [null] cannot be stored.
throw UnsupportedError('Length setter is not supported.');
}
@override
void add(E element) {
insert(length, element);
}
@override
void addAll(Iterable<E> iterable) {
iterable.forEach(add);
}
@override
void clear() {
_storage.clear();
}
@override
bool contains(Object element) {
return indexOf(element) != -1;
}
List<OrderedListTreePath> _sortedKeysList() {
return _storage.keys.toList()..sort();
}
List<E> _valuesList() {
return _sortedKeysList().map((key) => _storage[key]).toList();
}
@override
Change getChange() => _converter.serialize(_storage.getChange());
@override
void completeTransaction() {
_storage.completeTransaction();
}
@override
void applyChange(Change input) {
final ConvertedChange<OrderedListTreePath, E> change =
_converter.deserialize(input);
final keys = _sortedKeysList();
// We are add deleted keys in case this change is done on our
// connection. In other cases deletedKeys should be in keys.
final startKeys = (SplayTreeSet<OrderedListTreePath>()
..addAll(keys)
..addAll(change.deletedKeys))
.toList();
final deletedPositions = <int>[];
for (int i = 0; i < startKeys.length; i++) {
if (change.deletedKeys.contains(startKeys[i])) {
deletedPositions.add(i);
}
}
_storage.applyChange(change);
final keysFinal = _sortedKeysList();
final insertedElements = SplayTreeMap<int, E>();
for (int i = 0; i < keysFinal.length; i++) {
if (change.changedEntries.containsKey(keysFinal[i])) {
insertedElements[i] = change.changedEntries[keysFinal[i]];
}
}
_changeController
.add(OrderedListChange(deletedPositions, insertedElements));
}
@override
void rollbackChange() {
_storage.rollbackChange();
}
@override
set observer(ValueObserver observer) {
_storage.observer = observer;
}
Uint8List get _timestamp {
_incrementalTime += 1;
return Uint8List(8)..buffer.asByteData().setUint64(0, _incrementalTime);
}
}