diff --git a/lib/versioned-map.js b/lib/versioned-map.js index 6376925..30853cd 100644 --- a/lib/versioned-map.js +++ b/lib/versioned-map.js @@ -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); } } }