reactronic
Version:
Reactronic - Transactional Reactive State Management
302 lines (301 loc) • 8.76 kB
JavaScript
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--;
}
}