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