From d90411a6dd3f796bf8c44cb3d30d6ddedc0fbc8b Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Wed, 1 May 2019 14:47:39 +0200 Subject: [PATCH] adjust SortKey to have fragmentKey instead of gapKey with FragmentIndex to compare fragment keys --- doc/FRAGMENTS.md | 7 +- src/matrix/room/persister.js | 4 +- src/matrix/room/timeline/FragmentIndex.js | 49 ++--- src/matrix/room/timeline/SortKey.js | 197 ++++++++++++++++++ .../storage/idb/stores/RoomTimelineStore.js | 2 +- .../memory/stores/RoomTimelineStore.js | 4 +- src/matrix/storage/sortkey.js | 181 ---------------- 7 files changed, 231 insertions(+), 213 deletions(-) create mode 100644 src/matrix/room/timeline/SortKey.js delete mode 100644 src/matrix/storage/sortkey.js diff --git a/doc/FRAGMENTS.md b/doc/FRAGMENTS.md index fd082898..a12d1264 100644 --- a/doc/FRAGMENTS.md +++ b/doc/FRAGMENTS.md @@ -1,5 +1,10 @@ - DONE: write FragmentIndex - - adapt SortKey + - adapt SortKey ... naming! : + - FragmentIndex (index as in db index) + - compare(fragmentKeyA, fragmentKeyB) + - SortKey + - FragmentKey + - EventKey (we don't use id here because we already have event_id in the event) - write fragmentStore - adapt timelineStore - adapt persister diff --git a/src/matrix/room/persister.js b/src/matrix/room/persister.js index be065b34..11b94145 100644 --- a/src/matrix/room/persister.js +++ b/src/matrix/room/persister.js @@ -1,4 +1,4 @@ -import SortKey from "../storage/sortkey.js"; +import SortKey from "./timeline/SortKey.js"; import FragmentIndex from "./timeline/FragmentIndex.js"; function gapEntriesAreEqual(a, b) { @@ -31,6 +31,7 @@ export default class RoomPersister { constructor({roomId, storage}) { this._roomId = roomId; this._storage = storage; + // TODO: load fragmentIndex? this._lastSortKey = new SortKey(); } @@ -38,6 +39,7 @@ export default class RoomPersister { //fetch key here instead? const [lastEvent] = await txn.roomTimeline.lastEvents(this._roomId, 1); if (lastEvent) { + // TODO: load fragmentIndex? this._lastSortKey = new SortKey(lastEvent.sortKey); console.log("room persister load", this._roomId, this._lastSortKey.toString()); } else { diff --git a/src/matrix/room/timeline/FragmentIndex.js b/src/matrix/room/timeline/FragmentIndex.js index 4be2b315..6c31e2d2 100644 --- a/src/matrix/room/timeline/FragmentIndex.js +++ b/src/matrix/room/timeline/FragmentIndex.js @@ -1,10 +1,3 @@ -class Fragment { - constructor(previousId, nextId) { - this.previousId = previousId; - this.nextId = nextId; - } -} - /* lookups will be far more frequent than changing fragment order, so data structure should be optimized for fast lookup @@ -93,7 +86,7 @@ class Island { }); } - compareIds(idA, idB) { + compare(idA, idB) { const sortIndexA = this._idToSortIndex.get(idA); if (sortIndexA === undefined) { throw new Error(`first id ${idA} isn't part of this island`); @@ -126,7 +119,7 @@ export default class FragmentIndex { return island; } - compareIds(idA, idB) { + compare(idA, idB) { if (idA === idB) { return 0; } @@ -135,7 +128,7 @@ export default class FragmentIndex { if (islandA !== islandB) { throw new Error(`${idA} and ${idB} are on different islands, can't tell order`); } - return islandA.compareIds(idA, idB); + return islandA.compare(idA, idB); } rebuild(fragments) { @@ -166,6 +159,7 @@ export default class FragmentIndex { // } } +//#ifdef TESTS export function tests() { return { test_1_island_3_fragments(assert) { @@ -174,24 +168,24 @@ export function tests() { {id: 1, nextId: 2}, {id: 2, nextId: 3, previousId: 1}, ]); - assert(index.compareIds(1, 2) < 0); - assert(index.compareIds(2, 1) > 0); + assert(index.compare(1, 2) < 0); + assert(index.compare(2, 1) > 0); - assert(index.compareIds(1, 3) < 0); - assert(index.compareIds(3, 1) > 0); + assert(index.compare(1, 3) < 0); + assert(index.compare(3, 1) > 0); - assert(index.compareIds(2, 3) < 0); - assert(index.compareIds(3, 2) > 0); + assert(index.compare(2, 3) < 0); + assert(index.compare(3, 2) > 0); - assert.equal(index.compareIds(1, 1), 0); + assert.equal(index.compare(1, 1), 0); }, test_2_island_dont_compare(assert) { const index = new FragmentIndex([ {id: 1}, {id: 2}, ]); - assert.throws(() => index.compareIds(1, 2)); - assert.throws(() => index.compareIds(2, 1)); + assert.throws(() => index.compare(1, 2)); + assert.throws(() => index.compare(2, 1)); }, test_2_island_compare_internally(assert) { const index = new FragmentIndex([ @@ -202,16 +196,16 @@ export function tests() { ]); - assert(index.compareIds(1, 2) < 0); - assert(index.compareIds(11, 12) < 0); + assert(index.compare(1, 2) < 0); + assert(index.compare(11, 12) < 0); - assert.throws(() => index.compareIds(1, 11)); - assert.throws(() => index.compareIds(12, 2)); + assert.throws(() => index.compare(1, 11)); + assert.throws(() => index.compare(12, 2)); }, test_unknown_id(assert) { const index = new FragmentIndex([{id: 1}]); - assert.throws(() => index.compareIds(1, 2)); - assert.throws(() => index.compareIds(2, 1)); + assert.throws(() => index.compare(1, 2)); + assert.throws(() => index.compare(2, 1)); }, test_rebuild_flushes_old_state(assert) { const index = new FragmentIndex([ @@ -223,8 +217,9 @@ export function tests() { {id: 12, previousId: 11}, ]); - assert.throws(() => index.compareIds(1, 2)); - assert(index.compareIds(11, 12) < 0); + assert.throws(() => index.compare(1, 2)); + assert(index.compare(11, 12) < 0); }, } } +//#endif diff --git a/src/matrix/room/timeline/SortKey.js b/src/matrix/room/timeline/SortKey.js new file mode 100644 index 00000000..6ca6b549 --- /dev/null +++ b/src/matrix/room/timeline/SortKey.js @@ -0,0 +1,197 @@ +const MIN_INT32 = -2147483648; +const MID_INT32 = 0; +const MAX_INT32 = 2147483647; + +const MIN_UINT32 = 0; +const MID_UINT32 = 2147483647; +const MAX_UINT32 = 4294967295; + +const MIN = MIN_UINT32; +const MID = MID_UINT32; +const MAX = MAX_UINT32; + +export default class SortKey { + constructor(fragmentIndex, buffer) { + if (buffer) { + this._keys = new DataView(buffer); + } else { + this._keys = new DataView(new ArrayBuffer(8)); + // start default key right at the middle fragment key, min event key + // so we have the same amount of key address space either way + this.fragmentKey = MID; + this.eventKey = MIN; + } + this._fragmentIndex = fragmentIndex; + } + + get fragmentKey() { + return this._keys.getUint32(0, false); + } + + set fragmentKey(value) { + return this._keys.setUint32(0, value, false); + } + + get eventKey() { + return this._keys.getUint32(4, false); + } + + set eventKey(value) { + return this._keys.setUint32(4, value, false); + } + + get buffer() { + return this._keys.buffer; + } + + nextFragmentKey() { + const k = new SortKey(this._fragmentIndex); + k.fragmentKey = this.fragmentKey + 1; + k.eventKey = MIN; + return k; + } + + nextKey() { + const k = new SortKey(this._fragmentIndex); + k.fragmentKey = this.fragmentKey; + k.eventKey = this.eventKey + 1; + return k; + } + + previousKey() { + const k = new SortKey(this._fragmentIndex); + k.fragmentKey = this.fragmentKey; + k.eventKey = this.eventKey - 1; + return k; + } + + clone() { + const k = new SortKey(); + k.fragmentKey = this.fragmentKey; + k.eventKey = this.eventKey; + return k; + } + + static get maxKey() { + const maxKey = new SortKey(null); + maxKey.fragmentKey = MAX; + maxKey.eventKey = MAX; + return maxKey; + } + + static get minKey() { + const minKey = new SortKey(null); + minKey.fragmentKey = MIN; + minKey.eventKey = MIN; + return minKey; + } + + compare(otherKey) { + const fragmentDiff = this.fragmentKey - otherKey.fragmentKey; + if (fragmentDiff === 0) { + return this.eventKey - otherKey.eventKey; + } else { + // minKey and maxKey might not have fragmentIndex, so short-circuit this first ... + if (this.fragmentKey === MIN || otherKey.fragmentKey === MAX) { + return -1; + } + if (this.fragmentKey === MAX || otherKey.fragmentKey === MIN) { + return 1; + } + // ... then delegate to fragmentIndex. + // This might throw if the relation of two fragments is unknown. + return this._fragmentIndex.compare(this.fragmentKey, otherKey.fragmentKey); + } + } + + toString() { + return `[${this.fragmentKey}/${this.eventKey}]`; + } +} + +//#ifdef TESTS +export function tests() { + const fragmentIndex = {compare: (a, b) => a - b}; + + return { + test_default_key(assert) { + const k = new SortKey(fragmentIndex); + assert.equal(k.fragmentKey, MID); + assert.equal(k.eventKey, MIN); + }, + + test_inc(assert) { + const a = new SortKey(fragmentIndex); + const b = a.nextKey(); + assert.equal(a.fragmentKey, b.fragmentKey); + assert.equal(a.eventKey + 1, b.eventKey); + const c = b.previousKey(); + assert.equal(b.fragmentKey, c.fragmentKey); + assert.equal(c.eventKey + 1, b.eventKey); + assert.equal(a.eventKey, c.eventKey); + }, + + test_min_key(assert) { + const minKey = SortKey.minKey; + const k = new SortKey(fragmentIndex); + assert(minKey.fragmentKey <= k.fragmentKey); + assert(minKey.eventKey <= k.eventKey); + assert(k.compare(minKey) > 0); + assert(minKey.compare(k) < 0); + }, + + test_max_key(assert) { + const maxKey = SortKey.maxKey; + const k = new SortKey(fragmentIndex); + assert(maxKey.fragmentKey >= k.fragmentKey); + assert(maxKey.eventKey >= k.eventKey); + assert(k.compare(maxKey) < 0); + assert(maxKey.compare(k) > 0); + }, + + test_immutable(assert) { + const a = new SortKey(fragmentIndex); + const fragmentKey = a.fragmentKey; + const eventKey = a.eventKey; + a.nextFragmentKey(); + assert.equal(a.fragmentKey, fragmentKey); + assert.equal(a.eventKey, eventKey); + }, + + test_cmp_fragmentkey_first(assert) { + const a = new SortKey(fragmentIndex); + const b = new SortKey(fragmentIndex); + a.fragmentKey = 2; + a.eventKey = 1; + b.fragmentKey = 1; + b.eventKey = 100000; + assert(a.compare(b) > 0); + }, + + test_cmp_eventkey_second(assert) { + const a = new SortKey(fragmentIndex); + const b = new SortKey(fragmentIndex); + a.fragmentKey = 1; + a.eventKey = 100000; + b.fragmentKey = 1; + b.eventKey = 2; + assert(a.compare(b) > 0); + }, + + test_cmp_max_larger_than_min(assert) { + assert(SortKey.minKey.compare(SortKey.maxKey) < 0); + }, + + test_cmp_fragmentkey_first_large(assert) { + const a = new SortKey(fragmentIndex); + const b = new SortKey(fragmentIndex); + a.fragmentKey = MAX; + a.eventKey = MIN; + b.fragmentKey = MIN; + b.eventKey = MAX; + assert(b < a); + assert(a > b); + } + }; +} +//#endif diff --git a/src/matrix/storage/idb/stores/RoomTimelineStore.js b/src/matrix/storage/idb/stores/RoomTimelineStore.js index 491c1f10..8b47a501 100644 --- a/src/matrix/storage/idb/stores/RoomTimelineStore.js +++ b/src/matrix/storage/idb/stores/RoomTimelineStore.js @@ -1,4 +1,4 @@ -import SortKey from "../../sortkey.js"; +import SortKey from "../../../room/timeline/SortKey.js"; class Range { constructor(only, lower, upper, lowerOpen, upperOpen) { diff --git a/src/matrix/storage/memory/stores/RoomTimelineStore.js b/src/matrix/storage/memory/stores/RoomTimelineStore.js index 40c521cb..3c17c045 100644 --- a/src/matrix/storage/memory/stores/RoomTimelineStore.js +++ b/src/matrix/storage/memory/stores/RoomTimelineStore.js @@ -1,6 +1,6 @@ -import SortKey from "../sortkey.js"; +import SortKey from "../../room/timeline/SortKey.js"; import sortedIndex from "../../../utils/sortedIndex.js"; -import Store from "./Store"; +import Store from "./Store.js"; function compareKeys(key, entry) { if (key.roomId === entry.roomId) { diff --git a/src/matrix/storage/sortkey.js b/src/matrix/storage/sortkey.js deleted file mode 100644 index bfc15a5f..00000000 --- a/src/matrix/storage/sortkey.js +++ /dev/null @@ -1,181 +0,0 @@ -const MIN_INT32 = -2147483648; -const MID_INT32 = 0; -const MAX_INT32 = 2147483647; - -const MIN_UINT32 = 0; -const MID_UINT32 = 2147483647; -const MAX_UINT32 = 4294967295; - -const MIN = MIN_UINT32; -const MID = MID_UINT32; -const MAX = MAX_UINT32; - -export default class SortKey { - constructor(buffer) { - if (buffer) { - this._keys = new DataView(buffer); - } else { - this._keys = new DataView(new ArrayBuffer(8)); - // start default key right at the middle gap key, min event key - // so we have the same amount of key address space either way - this.gapKey = MID; - this.eventKey = MIN; - } - } - - get gapKey() { - return this._keys.getUint32(0, false); - } - - set gapKey(value) { - return this._keys.setUint32(0, value, false); - } - - get eventKey() { - return this._keys.getUint32(4, false); - } - - set eventKey(value) { - return this._keys.setUint32(4, value, false); - } - - get buffer() { - return this._keys.buffer; - } - - nextKeyWithGap() { - const k = new SortKey(); - k.gapKey = this.gapKey + 1; - k.eventKey = MIN; - return k; - } - - nextKey() { - const k = new SortKey(); - k.gapKey = this.gapKey; - k.eventKey = this.eventKey + 1; - return k; - } - - previousKey() { - const k = new SortKey(); - k.gapKey = this.gapKey; - k.eventKey = this.eventKey - 1; - return k; - } - - clone() { - const k = new SortKey(); - k.gapKey = this.gapKey; - k.eventKey = this.eventKey; - return k; - } - - static get maxKey() { - const maxKey = new SortKey(); - maxKey.gapKey = MAX; - maxKey.eventKey = MAX; - return maxKey; - } - - static get minKey() { - const minKey = new SortKey(); - minKey.gapKey = MIN; - minKey.eventKey = MIN; - return minKey; - } - - compare(otherKey) { - const gapDiff = this.gapKey - otherKey.gapKey; - if (gapDiff === 0) { - return this.eventKey - otherKey.eventKey; - } else { - return gapDiff; - } - } - - toString() { - return `[${this.gapKey}/${this.eventKey}]`; - } -} - -//#ifdef TESTS -export function tests() { - return { - test_default_key(assert) { - const k = new SortKey(); - assert.equal(k.gapKey, MID); - assert.equal(k.eventKey, MIN); - }, - - test_inc(assert) { - const a = new SortKey(); - const b = a.nextKey(); - assert.equal(a.gapKey, b.gapKey); - assert.equal(a.eventKey + 1, b.eventKey); - const c = b.previousKey(); - assert.equal(b.gapKey, c.gapKey); - assert.equal(c.eventKey + 1, b.eventKey); - assert.equal(a.eventKey, c.eventKey); - }, - - test_min_key(assert) { - const minKey = SortKey.minKey; - const k = new SortKey(); - assert(minKey.gapKey <= k.gapKey); - assert(minKey.eventKey <= k.eventKey); - }, - - test_max_key(assert) { - const maxKey = SortKey.maxKey; - const k = new SortKey(); - assert(maxKey.gapKey >= k.gapKey); - assert(maxKey.eventKey >= k.eventKey); - }, - - test_immutable(assert) { - const a = new SortKey(); - const gapKey = a.gapKey; - const eventKey = a.eventKey; - a.nextKeyWithGap(); - assert.equal(a.gapKey, gapKey); - assert.equal(a.eventKey, eventKey); - }, - - test_cmp_gapkey_first(assert) { - const a = new SortKey(); - const b = new SortKey(); - a.gapKey = 2; - a.eventKey = 1; - b.gapKey = 1; - b.eventKey = 100000; - assert(a.compare(b) > 0); - }, - - test_cmp_eventkey_second(assert) { - const a = new SortKey(); - const b = new SortKey(); - a.gapKey = 1; - a.eventKey = 100000; - b.gapKey = 1; - b.eventKey = 2; - assert(a.compare(b) > 0); - }, - - test_cmp_max_larger_than_min(assert) { - assert(SortKey.minKey.compare(SortKey.maxKey) < 0); - }, - - test_cmp_gapkey_first_large(assert) { - const a = new SortKey(); - const b = new SortKey(); - a.gapKey = MAX; - a.eventKey = MIN; - b.gapKey = MIN; - b.eventKey = MAX; - assert(b < a); - assert(a > b); - } - }; -} -//#endif