Optimize VersionedMap

This commit is contained in:
Zaid Abdulla committed 2018-08-27 00:20:10 -07:00
1 parent d3bb5ae16f
commit 4b4bcb4516
1 file changed
+84 -130
+84 -130
View File
@@ -21,20 +21,6 @@
const NONEXISTENT = new Object(); const NONEXISTENT = new Object();
const UNDEFINED = undefined; const UNDEFINED = undefined;
function compareEquality(a, b) {
var t = typeof a;
if (
t === "number" ||
a == null ||
t === "boolean" ||
t === "string" ||
t === "symbol"
)
return a === b;
return JSON.stringify(a) === JSON.stringify(b);
}
class VersionedMap { class VersionedMap {
constructor() { constructor() {
this._sizeDiff = [0]; this._sizeDiff = [0];
@@ -58,155 +44,127 @@ class VersionedMap {
this._revision = rev; this._revision = rev;
} }
get(key, rev) { get(key, rev = this._revision) {
if (rev == null) rev = this._revision; const revisions = this.map.get(key);
if (!revisions) return UNDEFINED;
var v = this.map.get(key); const v = revisions[Math.min(revisions.length - 1, rev)];
if (!v) return UNDEFINED; if (v === NONEXISTENT) return UNDEFINED;
return v;
for (let i = Math.min(v.length - 1, rev); i >= 0; --i) {
if (i in v) {
if (v[i] === NONEXISTENT) return UNDEFINED;
else return v[i];
}
}
return UNDEFINED;
} }
has(key, rev) { has(key, rev = this._revision) {
if (rev == null) rev = this._revision; const revisions = this.map.get(key);
if (!revisions) return false;
var v = this.map.get(key); const v = revisions[Math.min(revisions.length - 1, rev)];
if (v === NONEXISTENT) return false;
if (v == null) return false; return true;
for (let i = Math.min(v.length - 1, rev); i >= 0; --i) {
if (i in v) {
if (v[i] === NONEXISTENT) return false;
else return true;
}
}
return false;
} }
set(key, value, rev) { set(key, value, rev = this._revision) {
if (rev == null) rev = this._revision; let revisions = this.map.get(key);
if (!revisions) {
var v = this.map.get(key);
if (!v) {
this.dirty |= 1 << rev; this.dirty |= 1 << rev;
for (let i = 0; i < rev; ++i) this._sizeDiff[i] -= 1; for (let i = 0; i < rev; ++i) this._sizeDiff[i] -= 1;
v = []; revisions = [];
v[rev] = value; for (let i = 0; i < rev; ++i) revisions[i] = NONEXISTENT;
this.map.set(key, v); revisions[rev] = value;
return; this.map.set(key, revisions);
return true;
} }
var old = v[Math.min(rev, v.length - 1)]; // Can't modify old revisions
if (rev < revisions.length - 1) return null;
if (rev < v.length - 1) { const old = revisions[revisions.length - 1];
if (compareEquality(value, old)) return;
else throw new Error("Cannot modify old revisions");
}
this.dirty |= 1 << rev; this.dirty |= 1 << rev;
if (old === NONEXISTENT) ++this._sizeDiff[rev]; if (old === NONEXISTENT) ++this._sizeDiff[rev];
v[rev] = value; for (let i = revisions.length; i < rev; ++i) revisions[i] = old;
revisions[rev] = value;
return true;
} }
delete(key, rev) { delete(key, rev = this._revision) {
if (rev == null) rev = this._revision; const revisions = this.map.get(key);
if (!revisions) return false;
var v = this.map.get(key); // Can't modify old revisions
if (!v) return false; if (rev < revisions.length - 1) return null;
if (rev < v.length - 1) throw new Error("Cannot modify old revisions"); const old = revisions[revisions.length - 1];
var old = v[v.length - 1];
if (old === NONEXISTENT) return false; if (old === NONEXISTENT) return false;
this.dirty |= 1 << rev; this.dirty |= 1 << rev;
--this._sizeDiff[rev]; --this._sizeDiff[rev];
v[rev] = NONEXISTENT;
for (let i = revisions.length; i < rev; ++i) revisions[i] = old;
revisions[rev] = NONEXISTENT;
return true; return true;
} }
getRevisions(key) { getRevisions(key) {
var v = this.map.get(key); const revisions = this.map.get(key);
if (!v) return null; if (!revisions) return null;
var res = {}; const res = {};
for (const i in v) { let prev = NONEXISTENT;
if (v[i] === NONEXISTENT) res.delete |= 1 << i; for (const [i, v] of revisions.entries()) {
else res[i] = v[i]; if (v === prev) continue;
if (v === NONEXISTENT) res.delete |= 1 << i;
else res[i] = v;
prev = v;
} }
return res; return res;
} }
setRevisions(key, revisions) { setRevisions(key, revisionsObj) {
var del = 0; const del = revisionsObj.delete || 0;
var rev = []; const maxRev = Object.keys(revisionsObj).reduce(
var minKey = 999; (acc, cur) => (+cur > acc ? +cur : acc),
for (const k in revisions) { 0
if (k === "delete") { );
del = revisions[k];
} else {
const r = parseInt(k);
minKey = Math.min(minKey, r);
for (let i = this._sizeDiff.length; i <= r; ++i)
this._sizeDiff[i] = this._sizeDiff[i - 1];
this.dirty |= 1 << r; const revisions = [];
rev[r] = revisions[k];
} let prev = NONEXISTENT;
for (let i = 0; i <= maxRev; ++i) {
let v = prev;
if (del & (1 << i)) v = NONEXISTENT;
else if (i in revisionsObj) v = revisionsObj[i];
if (v !== prev) this.dirty |= 1 << i;
revisions[i] = v;
prev = v;
} }
for (let i = 0; i < minKey; ++i) --this._sizeDiff[i]; this.map.set(key, revisions);
this.dirty |= del;
for (let i = 0; del > 0; del >>= 1, ++i) if (del & 1) rev[i] = NONEXISTENT;
this.map.set(key, rev);
} }
getDiff(key) { getDiff(key) {
var revisions = this.map.get(key); const revisions = this.map.get(key);
if (!revisions) return []; if (!revisions) return [UNDEFINED, UNDEFINED];
let first = revisions[0];
var current = revisions[revisions.length - 1]; if (first === NONEXISTENT) first = UNDEFINED;
if (current === NONEXISTENT) { let last = revisions[revisions.length - 1];
if (0 in revisions) return [key, revisions[0]]; if (last === NONEXISTENT) last = UNDEFINED;
else return [key]; return [first, last];
} else if (0 in revisions) {
return [key, revisions[0], current];
} else {
// TODO don't use sparse arrays?
// eslint-disable-next-line no-sparse-arrays
return [key, , current];
}
} }
*diff() { *diff() {
for (const pair of this.map) { for (const [key, revisions] of this.map) {
const current = pair[1][pair[1].length - 1]; let first = revisions[0];
if (current === NONEXISTENT) { let last = revisions[revisions.length - 1];
if (0 in pair[1]) yield [pair[0], pair[1][0]]; if ((first === last) === NONEXISTENT) continue;
else yield [pair[0]]; if (first === NONEXISTENT) first = UNDEFINED;
} else if (0 in pair[1]) { if (last === NONEXISTENT) last = UNDEFINED;
yield [pair[0], pair[1][0], current]; yield [key, first, last];
} else {
// TODO don't use sparse arrays?
// eslint-disable-next-line no-sparse-arrays
yield [pair[0], , current];
}
} }
} }
@@ -220,18 +178,14 @@ class VersionedMap {
this.dirty = this.dirty ^ (d << revision); this.dirty = this.dirty ^ (d << revision);
this.dirty |= +!!d << revision; this.dirty |= +!!d << revision;
for (const pair of this.map) { for (const [k, v] of this.map) {
const k = pair[0]; const l = v.length - 1;
const v = pair[1]; if (l <= revision) continue;
const last = v[v.length - 1]; const last = v[l];
if (last === NONEXISTENT) { v.splice(revision, l - revision);
v.splice(revision, v.length);
if (v.some(() => true)) v[revision] = NONEXISTENT; if (last === NONEXISTENT && !v.some(vv => vv !== NONEXISTENT))
else this.map.delete(k); this.map.delete(k);
} else {
v[revision] = last;
v.splice(revision + 1, v.length);
}
} }
} }
} }