reactronic
Version:
Reactronic - Transactional Reactive State Management
300 lines (299 loc) • 8.67 kB
JavaScript
export class MergeList {
constructor(getKey, strict = false) {
this.getKey = getKey;
this.strict = strict;
this.map = new Map();
this.tag = ~0;
this.current = new MergeItemChain();
this.added = new MergeItemChain();
this.removed = new MergeItemChain();
this.lastNotFoundKey = undefined;
this.strictNextItem = undefined;
}
get isStrict() { return this.strict; }
set isStrict(value) {
if (this.isMergeInProgress && this.current.count > 0)
throw new Error("cannot change strict mode in the middle of merge");
this.strict = value;
}
get count() {
return this.current.count;
}
get addedCount() {
return this.added.count;
}
get removedCount() {
return this.removed.count;
}
get isMergeInProgress() {
return this.tag > 0;
}
lookup(key) {
let result = undefined;
if (key !== undefined && key !== this.lastNotFoundKey) {
result = this.map.get(key);
if (result) {
if (this.getKey(result.instance) !== key) {
this.lastNotFoundKey = key;
result = undefined;
}
}
else
this.lastNotFoundKey = key;
}
return result;
}
tryMergeAsExisting(key, resolution, error) {
const tag = this.tag;
if (tag < 0)
throw new Error(error !== null && error !== void 0 ? error : "merge is not in progress");
let item = this.strictNextItem;
if (key !== (item ? this.getKey(item.instance) : undefined))
item = this.lookup(key);
if (item) {
if (item.tag !== tag) {
item.tag = tag;
if (this.strict && item !== this.strictNextItem)
item.status = tag;
this.strictNextItem = item.next;
this.removed.exclude(item);
item.index = this.current.count;
this.current.include(item);
if (resolution)
resolution.isDuplicate = false;
}
else if (resolution)
resolution.isDuplicate = true;
else
throw new Error(`duplicate collection item: ${key}`);
}
else if (resolution)
resolution.isDuplicate = false;
return item;
}
mergeAsAdded(instance) {
const key = this.getKey(instance);
if (this.lookup(key) !== undefined)
throw new Error(`key is already in use: ${key}`);
let tag = this.tag;
if (tag < 0) {
tag = ~this.tag + 1;
this.tag = ~tag;
}
const item = new MergedItemImpl(instance, tag);
this.map.set(key, item);
this.lastNotFoundKey = undefined;
this.strictNextItem = undefined;
item.index = this.current.count;
this.current.include(item);
this.added.aux(item);
return item;
}
mergeAsRemoved(item) {
const t = item;
if (!this.isRemoved(t)) {
this.current.exclude(t);
this.removed.include(t);
t.tag--;
}
}
move(item, after) {
throw new Error("not implemented");
}
beginMerge() {
if (this.isMergeInProgress)
throw new Error("merge is in progress already");
this.tag = ~this.tag + 1;
this.strictNextItem = this.current.first;
this.removed.grab(this.current, false);
this.added.reset();
}
endMerge(error) {
if (!this.isMergeInProgress)
throw new Error("merge is ended already");
this.tag = ~this.tag;
if (error === undefined) {
const currentCount = this.current.count;
if (currentCount > 0) {
const getKey = this.getKey;
if (currentCount > this.removed.count) {
const map = this.map;
for (const x of this.removed.items())
map.delete(getKey(x.instance));
}
else {
const map = this.map = new Map();
for (const x of this.current.items())
map.set(getKey(x.instance), x);
}
}
else
this.map = new Map();
}
else {
this.current.grab(this.removed, true);
const getKey = this.getKey;
for (const x of this.added.itemsViaAux()) {
this.map.delete(getKey(x.instance));
this.current.exclude(x);
}
this.added.reset();
}
}
resetAddedAndRemovedLists() {
this.removed.reset();
this.added.reset();
}
firstMergedItem() {
return this.current.first;
}
lastMergedItem() {
return this.current.last;
}
*items(onlyAfter) {
var _a;
let x = (_a = onlyAfter === null || onlyAfter === void 0 ? void 0 : onlyAfter.next) !== null && _a !== void 0 ? _a : this.current.first;
while (x !== undefined) {
const next = x.next;
yield x;
x = next;
}
}
*addedItems(reset) {
let x = this.added.first;
while (x !== undefined) {
const next = x.aux;
if (!this.isRemoved(x))
yield x;
x = next;
}
if (reset)
this.added.reset();
}
*removedItems(reset) {
let x = this.removed.first;
while (x !== undefined) {
const next = x.next;
yield x;
x = next;
}
if (reset)
this.removed.reset();
}
isAdded(item) {
const t = item;
let tag = this.tag;
if (tag < 0)
tag = ~tag;
return t.status === ~tag && t.tag > 0;
}
isMoved(item) {
const t = item;
let tag = this.tag;
if (tag < 0)
tag = ~tag;
return t.status === tag && t.tag > 0;
}
isRemoved(item) {
const t = item;
const tag = this.tag;
return tag > 0 ? t.tag < tag : t.tag < tag - 1;
}
isActual(item) {
const t = item;
return t.tag === this.tag;
}
markAsMoved(item) {
const t = item;
if (t.tag > 0)
t.status = t.tag;
}
static createItem(instance) {
return new MergedItemImpl(instance, 0);
}
}
class MergedItemImpl {
constructor(instance, tag) {
this.instance = instance;
this.index = -1;
this.tag = tag;
this.status = ~tag;
this.next = undefined;
this.prev = undefined;
this.aux = undefined;
}
}
class MergeItemChain {
constructor() {
this.count = 0;
this.first = undefined;
this.last = undefined;
}
*items() {
let x = this.first;
while (x !== undefined) {
const next = x.next;
yield x;
x = next;
}
}
*itemsViaAux() {
let x = this.first;
while (x !== undefined) {
const next = x.aux;
yield x;
x = next;
}
}
reset() {
this.count = 0;
this.first = undefined;
this.last = undefined;
}
grab(from, join) {
const head = from.first;
if (join && head) {
const last = this.last;
head.prev = last;
if (last)
this.last = last.next = head;
else
this.first = this.last = head;
this.count += from.count;
}
else {
this.count = from.count;
this.first = head;
this.last = from.last;
}
from.reset();
}
include(item) {
const last = this.last;
item.prev = last;
item.next = undefined;
if (last)
this.last = last.next = item;
else
this.first = this.last = item;
this.count++;
}
exclude(item) {
if (item.prev !== undefined)
item.prev.next = item.next;
if (item.next !== undefined)
item.next.prev = item.prev;
if (item === this.first)
this.first = item.next;
this.count--;
}
aux(item) {
item.aux = undefined;
const last = this.last;
if (last)
this.last = last.aux = item;
else
this.first = this.last = item;
this.count++;
}
}