UNPKG

reactronic

Version:

Reactronic - Transactional Reactive State Management

302 lines (301 loc) 8.76 kB
import { misuse } from "./Dbg.js"; export class ReconciliationList { constructor(getKey, strict = false) { this.getKey = getKey; this.strict = strict; this.map = new Map(); this.tag = ~0; this.actual = new LinkedItemChain(); this.added = new LinkedItemChain(); this.removed = new LinkedItemChain(); this.lastNotFoundKey = undefined; this.strictNextItem = undefined; } get isStrict() { return this.strict; } set isStrict(value) { if (this.isReconciliationInProgress && this.actual.count > 0) throw misuse("cannot change strict mode in the middle of reconciliation"); this.strict = value; } get count() { return this.actual.count; } get countOfAdded() { return this.added.count; } get countOfRemoved() { return this.removed.count; } get isReconciliationInProgress() { 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; } tryReuse(key, resolution, error) { const tag = this.tag; if (tag < 0) throw misuse(error !== null && error !== void 0 ? error : "reconciliation 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.moving = tag; this.strictNextItem = item.next; this.removed.exclude(item); item.index = this.actual.count; this.actual.include(item); if (resolution) resolution.isDuplicate = false; } else if (resolution) resolution.isDuplicate = true; else throw misuse(`duplicate collection item: ${key}`); } else if (resolution) resolution.isDuplicate = false; return item; } add(instance) { const key = this.getKey(instance); if (this.lookup(key) !== undefined) throw misuse(`key is already in use: ${key}`); const tag = this.tag > 0 ? this.tag : 0; const item = new LinkedItem$(instance, tag); this.map.set(key, item); this.lastNotFoundKey = undefined; this.strictNextItem = undefined; item.index = this.actual.count; this.actual.include(item); if (tag !== 0) this.added.includeAux(item); return item; } remove(item) { const t = item; if (!this.isRemoved(t)) { this.actual.exclude(t); this.removed.include(t); t.tag--; } } move(item, after) { throw misuse("not implemented"); } beginReconciliation() { if (this.isReconciliationInProgress) throw misuse("reconciliation is in progress already"); this.tag = ~this.tag + 1; this.strictNextItem = this.actual.first; this.removed.grab(this.actual, false); this.added.clear(); } endReconciliation(error) { if (!this.isReconciliationInProgress) throw misuse("reconciliation is ended already"); this.tag = ~this.tag; if (error === undefined) { const actualCount = this.actual.count; if (actualCount > 0) { const getKey = this.getKey; if (actualCount > 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.actual.items()) map.set(getKey(x.instance), x); } } else this.map = new Map(); } else { this.actual.grab(this.removed, true); const getKey = this.getKey; for (const x of this.added.itemsAux()) { this.map.delete(getKey(x.instance)); this.actual.exclude(x); } this.added.clear(); } } clearAddedAndRemoved() { this.removed.clear(); this.added.clear(); } firstItem() { return this.actual.first; } lastItem() { return this.actual.last; } *items(onlyAfter) { var _a; let x = (_a = onlyAfter === null || onlyAfter === void 0 ? void 0 : onlyAfter.next) !== null && _a !== void 0 ? _a : this.actual.first; while (x !== undefined) { const next = x.next; yield x; x = next; } } *itemsAdded(clear) { let x = this.added.first; while (x !== undefined) { const next = x.aux; if (!this.isRemoved(x)) yield x; x = next; } if (clear) this.added.clear(); } *itemsRemoved(clear) { let x = this.removed.first; while (x !== undefined) { const next = x.next; yield x; x = next; } if (clear) this.removed.clear(); } isAdded(item) { const t = item; let tag = this.tag; if (tag < 0) tag = ~tag; return t.moving === ~tag && t.tag > 0; } isMoved(item) { const t = item; let tag = this.tag; if (tag < 0) tag = ~tag; return t.moving === 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; } isExternal(item) { const t = item; return t.tag === 0; } markAsMoved(item) { const t = item; if (t.tag > 0) t.moving = t.tag; } static createItem(instance) { return new LinkedItem$(instance, 0); } } class LinkedItem$ { constructor(instance, tag) { this.instance = instance; this.index = -1; this.tag = tag; this.moving = ~tag; this.next = undefined; this.prev = undefined; this.aux = undefined; } } class LinkedItemChain { 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; } } *itemsAux() { let x = this.first; while (x !== undefined) { const next = x.aux; yield x; x = next; } } clear() { 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.clear(); } 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++; } includeAux(item) { item.aux = undefined; const last = this.last; if (last) this.last = last.aux = 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--; } }