UNPKG

reactronic

Version:

Reactronic - Transactional Reactive State Management

300 lines (299 loc) 8.67 kB
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++; } }