adjust SortKey to have fragmentKey instead of gapKey

with FragmentIndex to compare fragment keys
This commit is contained in:
Bruno Windels 2019-05-01 14:47:39 +02:00
parent 8670ab6331
commit d90411a6dd
7 changed files with 231 additions and 213 deletions

View File

@ -1,5 +1,10 @@
- DONE: write FragmentIndex - 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 - write fragmentStore
- adapt timelineStore - adapt timelineStore
- adapt persister - adapt persister

View File

@ -1,4 +1,4 @@
import SortKey from "../storage/sortkey.js"; import SortKey from "./timeline/SortKey.js";
import FragmentIndex from "./timeline/FragmentIndex.js"; import FragmentIndex from "./timeline/FragmentIndex.js";
function gapEntriesAreEqual(a, b) { function gapEntriesAreEqual(a, b) {
@ -31,6 +31,7 @@ export default class RoomPersister {
constructor({roomId, storage}) { constructor({roomId, storage}) {
this._roomId = roomId; this._roomId = roomId;
this._storage = storage; this._storage = storage;
// TODO: load fragmentIndex?
this._lastSortKey = new SortKey(); this._lastSortKey = new SortKey();
} }
@ -38,6 +39,7 @@ export default class RoomPersister {
//fetch key here instead? //fetch key here instead?
const [lastEvent] = await txn.roomTimeline.lastEvents(this._roomId, 1); const [lastEvent] = await txn.roomTimeline.lastEvents(this._roomId, 1);
if (lastEvent) { if (lastEvent) {
// TODO: load fragmentIndex?
this._lastSortKey = new SortKey(lastEvent.sortKey); this._lastSortKey = new SortKey(lastEvent.sortKey);
console.log("room persister load", this._roomId, this._lastSortKey.toString()); console.log("room persister load", this._roomId, this._lastSortKey.toString());
} else { } else {

View File

@ -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, lookups will be far more frequent than changing fragment order,
so data structure should be optimized for fast lookup 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); const sortIndexA = this._idToSortIndex.get(idA);
if (sortIndexA === undefined) { if (sortIndexA === undefined) {
throw new Error(`first id ${idA} isn't part of this island`); throw new Error(`first id ${idA} isn't part of this island`);
@ -126,7 +119,7 @@ export default class FragmentIndex {
return island; return island;
} }
compareIds(idA, idB) { compare(idA, idB) {
if (idA === idB) { if (idA === idB) {
return 0; return 0;
} }
@ -135,7 +128,7 @@ export default class FragmentIndex {
if (islandA !== islandB) { if (islandA !== islandB) {
throw new Error(`${idA} and ${idB} are on different islands, can't tell order`); 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) { rebuild(fragments) {
@ -166,6 +159,7 @@ export default class FragmentIndex {
// } // }
} }
//#ifdef TESTS
export function tests() { export function tests() {
return { return {
test_1_island_3_fragments(assert) { test_1_island_3_fragments(assert) {
@ -174,24 +168,24 @@ export function tests() {
{id: 1, nextId: 2}, {id: 1, nextId: 2},
{id: 2, nextId: 3, previousId: 1}, {id: 2, nextId: 3, previousId: 1},
]); ]);
assert(index.compareIds(1, 2) < 0); assert(index.compare(1, 2) < 0);
assert(index.compareIds(2, 1) > 0); assert(index.compare(2, 1) > 0);
assert(index.compareIds(1, 3) < 0); assert(index.compare(1, 3) < 0);
assert(index.compareIds(3, 1) > 0); assert(index.compare(3, 1) > 0);
assert(index.compareIds(2, 3) < 0); assert(index.compare(2, 3) < 0);
assert(index.compareIds(3, 2) > 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) { test_2_island_dont_compare(assert) {
const index = new FragmentIndex([ const index = new FragmentIndex([
{id: 1}, {id: 1},
{id: 2}, {id: 2},
]); ]);
assert.throws(() => index.compareIds(1, 2)); assert.throws(() => index.compare(1, 2));
assert.throws(() => index.compareIds(2, 1)); assert.throws(() => index.compare(2, 1));
}, },
test_2_island_compare_internally(assert) { test_2_island_compare_internally(assert) {
const index = new FragmentIndex([ const index = new FragmentIndex([
@ -202,16 +196,16 @@ export function tests() {
]); ]);
assert(index.compareIds(1, 2) < 0); assert(index.compare(1, 2) < 0);
assert(index.compareIds(11, 12) < 0); assert(index.compare(11, 12) < 0);
assert.throws(() => index.compareIds(1, 11)); assert.throws(() => index.compare(1, 11));
assert.throws(() => index.compareIds(12, 2)); assert.throws(() => index.compare(12, 2));
}, },
test_unknown_id(assert) { test_unknown_id(assert) {
const index = new FragmentIndex([{id: 1}]); const index = new FragmentIndex([{id: 1}]);
assert.throws(() => index.compareIds(1, 2)); assert.throws(() => index.compare(1, 2));
assert.throws(() => index.compareIds(2, 1)); assert.throws(() => index.compare(2, 1));
}, },
test_rebuild_flushes_old_state(assert) { test_rebuild_flushes_old_state(assert) {
const index = new FragmentIndex([ const index = new FragmentIndex([
@ -223,8 +217,9 @@ export function tests() {
{id: 12, previousId: 11}, {id: 12, previousId: 11},
]); ]);
assert.throws(() => index.compareIds(1, 2)); assert.throws(() => index.compare(1, 2));
assert(index.compareIds(11, 12) < 0); assert(index.compare(11, 12) < 0);
}, },
} }
} }
//#endif

View File

@ -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

View File

@ -1,4 +1,4 @@
import SortKey from "../../sortkey.js"; import SortKey from "../../../room/timeline/SortKey.js";
class Range { class Range {
constructor(only, lower, upper, lowerOpen, upperOpen) { constructor(only, lower, upper, lowerOpen, upperOpen) {

View File

@ -1,6 +1,6 @@
import SortKey from "../sortkey.js"; import SortKey from "../../room/timeline/SortKey.js";
import sortedIndex from "../../../utils/sortedIndex.js"; import sortedIndex from "../../../utils/sortedIndex.js";
import Store from "./Store"; import Store from "./Store.js";
function compareKeys(key, entry) { function compareKeys(key, entry) {
if (key.roomId === entry.roomId) { if (key.roomId === entry.roomId) {

View File

@ -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