switched to *standard* coding style
This commit is contained in:
@@ -1,208 +1,179 @@
|
||||
|
||||
type State = {
|
||||
user: string,
|
||||
clock: number
|
||||
};
|
||||
|
||||
type StateVector = Array<State>;
|
||||
|
||||
type StateSet = Object;
|
||||
|
||||
type IDBTransaction = Function;
|
||||
type IDBObjectStore = Function;
|
||||
type IDBRequest = Function;
|
||||
type IDBCursor = Function;
|
||||
type IDBKeyRange = Function;
|
||||
|
||||
type IDBOpenDBRequest = Function;
|
||||
|
||||
declare var indexedDB : Object;
|
||||
|
||||
Y.IndexedDB = (function(){ //eslint-disable-line no-unused-vars
|
||||
class Transaction extends AbstractTransaction { //eslint-disable-line
|
||||
transaction: IDBTransaction;
|
||||
sv: IDBObjectStore;
|
||||
os: IDBObjectStore;
|
||||
store: OperationStore;
|
||||
|
||||
constructor (store : OperationStore) {
|
||||
super(store);
|
||||
this.transaction = store.db.transaction(["OperationStore", "StateVector"], "readwrite");
|
||||
this.sv = this.transaction.objectStore("StateVector");
|
||||
this.os = this.transaction.objectStore("OperationStore");
|
||||
this.buffer = {};
|
||||
Y.IndexedDB = (function () { // eslint-disable-line
|
||||
class Transaction extends AbstractTransaction { // eslint-disable-line
|
||||
constructor (store) {
|
||||
super(store)
|
||||
this.transaction = store.db.transaction(['OperationStore', 'StateVector'], 'readwrite')
|
||||
this.sv = this.transaction.objectStore('StateVector')
|
||||
this.os = this.transaction.objectStore('OperationStore')
|
||||
this.buffer = {}
|
||||
}
|
||||
*setOperation (op) {
|
||||
yield this.os.put(op);
|
||||
this.buffer[JSON.stringify(op.id)] = op;
|
||||
return op;
|
||||
* setOperation (op) {
|
||||
yield this.os.put(op)
|
||||
this.buffer[JSON.stringify(op.id)] = op
|
||||
return op
|
||||
}
|
||||
*getOperation (id) {
|
||||
var op = this.buffer[JSON.stringify(id)];
|
||||
* getOperation (id) {
|
||||
var op = this.buffer[JSON.stringify(id)]
|
||||
if (op == null) {
|
||||
op = yield this.os.get(id);
|
||||
this.buffer[JSON.stringify(id)] = op;
|
||||
op = yield this.os.get(id)
|
||||
this.buffer[JSON.stringify(id)] = op
|
||||
}
|
||||
return op;
|
||||
return op
|
||||
}
|
||||
*removeOperation (id) {
|
||||
this.buffer[JSON.stringify(id)] = null;
|
||||
return yield this.os.delete(id);
|
||||
* removeOperation (id) {
|
||||
this.buffer[JSON.stringify(id)] = null
|
||||
return yield this.os.delete(id)
|
||||
}
|
||||
*setState (state : State) : State {
|
||||
return yield this.sv.put(state);
|
||||
* setState (state) {
|
||||
return yield this.sv.put(state)
|
||||
}
|
||||
*getState (user : string) : State {
|
||||
var state;
|
||||
if ((state = yield this.sv.get(user)) != null){
|
||||
return state;
|
||||
* getState (user) {
|
||||
var state
|
||||
if ((state = yield this.sv.get(user)) != null) {
|
||||
return state
|
||||
} else {
|
||||
return {
|
||||
user: user,
|
||||
clock: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
*getStateVector () : StateVector {
|
||||
var stateVector = [];
|
||||
var cursorResult = this.sv.openCursor();
|
||||
var cursor;
|
||||
while ((cursor = yield cursorResult) != null) {
|
||||
stateVector.push(cursor.value);
|
||||
cursor.continue();
|
||||
}
|
||||
return stateVector;
|
||||
}
|
||||
*getStateSet () : StateSet {
|
||||
var sv : StateVector = yield* this.getStateVector();
|
||||
var ss : StateSet = {};
|
||||
for (var state of sv){
|
||||
ss[state.user] = state.clock;
|
||||
}
|
||||
return ss;
|
||||
}
|
||||
|
||||
*getOperations (startSS : StateSet) {
|
||||
if (startSS == null){
|
||||
startSS = {};
|
||||
}
|
||||
var ops = [];
|
||||
|
||||
var endSV : StateVector = yield* this.getStateVector();
|
||||
for (var endState of endSV) {
|
||||
var user = endState.user;
|
||||
var startPos = startSS[user] || 0;
|
||||
var endPos = endState.clock;
|
||||
var range = IDBKeyRange.bound([user, startPos], [user, endPos]);
|
||||
var cursorResult = this.os.openCursor(range);
|
||||
var cursor;
|
||||
while ((cursor = yield cursorResult) != null) {
|
||||
ops.push(cursor.value);
|
||||
cursor.continue();
|
||||
}
|
||||
}
|
||||
return ops;
|
||||
}
|
||||
* getStateVector () {
|
||||
var stateVector = []
|
||||
var cursorResult = this.sv.openCursor()
|
||||
var cursor
|
||||
while ((cursor = yield cursorResult) != null) {
|
||||
stateVector.push(cursor.value)
|
||||
cursor.continue()
|
||||
}
|
||||
return stateVector
|
||||
}
|
||||
* getStateSet () {
|
||||
var sv = yield* this.getStateVector()
|
||||
var ss = {}
|
||||
for (var state of sv) {
|
||||
ss[state.user] = state.clock
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
* getOperations (startSS) {
|
||||
if (startSS == null) {
|
||||
startSS = {}
|
||||
}
|
||||
var ops = []
|
||||
|
||||
var endSV = yield* this.getStateVector()
|
||||
for (var endState of endSV) {
|
||||
var user = endState.user
|
||||
var startPos = startSS[user] || 0
|
||||
var endPos = endState.clock
|
||||
var range = window.IDBKeyRange.bound([user, startPos], [user, endPos])
|
||||
var cursorResult = this.os.openCursor(range)
|
||||
var cursor
|
||||
while ((cursor = yield cursorResult) != null) {
|
||||
ops.push(cursor.value)
|
||||
cursor.continue()
|
||||
}
|
||||
}
|
||||
return ops
|
||||
}
|
||||
}
|
||||
class OperationStore extends AbstractOperationStore { //eslint-disable-line no-undef
|
||||
namespace: string;
|
||||
ready: Promise;
|
||||
whenReadyListeners: Array<Function>;
|
||||
class OperationStore extends AbstractOperationStore { // eslint-disable-line no-undef
|
||||
constructor (y, opts) {
|
||||
super(y);
|
||||
super(y)
|
||||
if (opts == null) {
|
||||
opts = {};
|
||||
opts = {}
|
||||
}
|
||||
if (opts.namespace == null || typeof opts.namespace !== "string") {
|
||||
throw new Error("IndexedDB: expect a string (opts.namespace)!");
|
||||
if (opts.namespace == null || typeof opts.namespace !== 'string') {
|
||||
throw new Error('IndexedDB: expect a string (opts.namespace)!')
|
||||
} else {
|
||||
this.namespace = opts.namespace;
|
||||
this.namespace = opts.namespace
|
||||
}
|
||||
if (opts.idbVersion != null) {
|
||||
this.idbVersion = opts.idbVersion;
|
||||
this.idbVersion = opts.idbVersion
|
||||
} else {
|
||||
this.idbVersion = 5;
|
||||
this.idbVersion = 5
|
||||
}
|
||||
|
||||
this.transactionQueue = {
|
||||
queue: [],
|
||||
onRequest: null
|
||||
};
|
||||
}
|
||||
|
||||
var store = this;
|
||||
var store = this
|
||||
|
||||
var tGen = (function *transactionGen(){
|
||||
store.db = yield indexedDB.open(opts.namespace, store.idbVersion);
|
||||
var transactionQueue = store.transactionQueue;
|
||||
var tGen = (function * transactionGen () {
|
||||
store.db = yield window.indexedDB.open(opts.namespace, store.idbVersion)
|
||||
var transactionQueue = store.transactionQueue
|
||||
|
||||
var transaction = null;
|
||||
var cont = true;
|
||||
var transaction = null
|
||||
var cont = true
|
||||
while (cont) {
|
||||
var request = yield transactionQueue;
|
||||
transaction = new Transaction(store);
|
||||
var request = yield transactionQueue
|
||||
transaction = new Transaction(store)
|
||||
|
||||
yield* request.call(transaction, request);/*
|
||||
yield* request.call(transaction, request) /*
|
||||
while (transactionQueue.queue.length > 0) {
|
||||
yield* transactionQueue.queue.shift().call(transaction);
|
||||
yield* transactionQueue.queue.shift().call(transaction)
|
||||
}*/
|
||||
}
|
||||
})();
|
||||
})()
|
||||
|
||||
function handleTransactions(t){ //eslint-disable-line no-unused-vars
|
||||
var request = t.value;
|
||||
if (t.done){
|
||||
return;
|
||||
} else if (request.constructor === IDBRequest
|
||||
|| request.constructor === IDBCursor ) {
|
||||
request.onsuccess = function(){
|
||||
handleTransactions(tGen.next(request.result));
|
||||
};
|
||||
request.onerror = function(err){
|
||||
tGen.throw(err);
|
||||
};
|
||||
} else if (request === store.transactionQueue) {
|
||||
if (request.queue.length > 0){
|
||||
handleTransactions(tGen.next(request.queue.shift()));
|
||||
} else {
|
||||
request.onRequest = function(){
|
||||
request.onRequest = null;
|
||||
handleTransactions(tGen.next(request.queue.shift()));
|
||||
};
|
||||
function handleTransactions (t) { // eslint-disable-line no-unused-vars
|
||||
var request = t.value
|
||||
if (t.done) {
|
||||
return
|
||||
} else if (request.constructor === window.IDBRequest || request.constructor === window.IDBCursor) {
|
||||
request.onsuccess = function () {
|
||||
handleTransactions(tGen.next(request.result))
|
||||
}
|
||||
} else if ( request.constructor === IDBOpenDBRequest ) {
|
||||
request.onsuccess = function(event){
|
||||
var db = event.target.result;
|
||||
handleTransactions(tGen.next(db));
|
||||
};
|
||||
request.onerror = function(){
|
||||
tGen.throw("Couldn't open IndexedDB database!");
|
||||
};
|
||||
request.onupgradeneeded = function(event){
|
||||
var db = event.target.result;
|
||||
try {
|
||||
db.createObjectStore("OperationStore", {keyPath: "id"});
|
||||
db.createObjectStore("StateVector", {keyPath: "user"});
|
||||
} catch (e) {
|
||||
// console.log("Store already exists!");
|
||||
request.onerror = function (err) {
|
||||
tGen.throw(err)
|
||||
}
|
||||
} else if (request === store.transactionQueue) {
|
||||
if (request.queue.length > 0) {
|
||||
handleTransactions(tGen.next(request.queue.shift()))
|
||||
} else {
|
||||
request.onRequest = function () {
|
||||
request.onRequest = null
|
||||
handleTransactions(tGen.next(request.queue.shift()))
|
||||
}
|
||||
};
|
||||
}
|
||||
} else if (request.constructor === window.IDBOpenDBRequest) {
|
||||
request.onsuccess = function (event) {
|
||||
var db = event.target.result
|
||||
handleTransactions(tGen.next(db))
|
||||
}
|
||||
request.onerror = function () {
|
||||
tGen.throw("Couldn't open IndexedDB database!")
|
||||
}
|
||||
request.onupgradeneeded = function (event) {
|
||||
var db = event.target.result
|
||||
try {
|
||||
db.createObjectStore('OperationStore', {keyPath: 'id'})
|
||||
db.createObjectStore('StateVector', {keyPath: 'user'})
|
||||
} catch (e) {
|
||||
// console.log("Store already exists!")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tGen.throw("You can not yield this type!");
|
||||
tGen.throw('You can not yield this type!')
|
||||
}
|
||||
}
|
||||
handleTransactions(tGen.next());
|
||||
handleTransactions(tGen.next())
|
||||
|
||||
}
|
||||
requestTransaction (makeGen : Function) {
|
||||
this.transactionQueue.queue.push(makeGen);
|
||||
requestTransaction (makeGen) {
|
||||
this.transactionQueue.queue.push(makeGen)
|
||||
if (this.transactionQueue.onRequest != null) {
|
||||
this.transactionQueue.onRequest();
|
||||
this.transactionQueue.onRequest()
|
||||
}
|
||||
}
|
||||
*removeDatabase () {
|
||||
this.db.close();
|
||||
yield indexedDB.deleteDatabase(this.namespace);
|
||||
* removeDatabase () {
|
||||
this.db.close()
|
||||
yield window.indexedDB.deleteDatabase(this.namespace)
|
||||
}
|
||||
}
|
||||
return OperationStore;
|
||||
})();
|
||||
return OperationStore
|
||||
})()
|
||||
|
||||
@@ -1,117 +1,117 @@
|
||||
/* @flow */
|
||||
/*eslint-env browser,jasmine */
|
||||
/* global Y */
|
||||
/* eslint-env browser,jasmine */
|
||||
|
||||
if(typeof window !== "undefined"){
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
|
||||
describe("IndexedDB", function() {
|
||||
var ob;
|
||||
beforeAll(function(){
|
||||
ob = new Y.IndexedDB(null, {namespace: "Test"});
|
||||
});
|
||||
if (typeof window !== 'undefined') {
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000
|
||||
describe('IndexedDB', function () {
|
||||
var ob
|
||||
beforeAll(function () {
|
||||
ob = new Y.IndexedDB(null, {namespace: 'Test'})
|
||||
})
|
||||
|
||||
it("can add and get operation", function(done) {
|
||||
ob.requestTransaction(function*(){
|
||||
it('can add and get operation', function (done) {
|
||||
ob.requestTransaction(function *() {
|
||||
var op = yield* this.setOperation({
|
||||
"id": ["1", 0],
|
||||
"stuff": true
|
||||
});
|
||||
expect(yield* this.getOperation(["1", 0]))
|
||||
.toEqual(op);
|
||||
done();
|
||||
});
|
||||
});
|
||||
'id': ['1', 0],
|
||||
'stuff': true
|
||||
})
|
||||
expect(yield* this.getOperation(['1', 0]))
|
||||
.toEqual(op)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it("can remove operation", function(done) {
|
||||
ob.requestTransaction(function*(){
|
||||
it('can remove operation', function (done) {
|
||||
ob.requestTransaction(function *() {
|
||||
var op = yield* this.setOperation({
|
||||
"id": ["1", 0],
|
||||
"stuff": true
|
||||
});
|
||||
expect(yield* this.getOperation(["1", 0]))
|
||||
.toEqual(op);
|
||||
yield* this.removeOperation(["1", 0]);
|
||||
expect(yield* this.getOperation(["1", 0]))
|
||||
.toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
'id': ['1', 0],
|
||||
'stuff': true
|
||||
})
|
||||
expect(yield* this.getOperation(['1', 0]))
|
||||
.toEqual(op)
|
||||
yield* this.removeOperation(['1', 0])
|
||||
expect(yield* this.getOperation(['1', 0]))
|
||||
.toBeUndefined()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it("getOperation(op) returns undefined if op does not exist", function(done){
|
||||
ob.requestTransaction(function*(){
|
||||
var op = yield* this.getOperation("plzDon'tBeThere");
|
||||
expect(op).toBeUndefined();
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('getOperation(op) returns undefined if op does not exist', function (done) {
|
||||
ob.requestTransaction(function *() {
|
||||
var op = yield* this.getOperation("plzDon'tBeThere")
|
||||
expect(op).toBeUndefined()
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it("yield throws if request is unknown", function(done){
|
||||
ob.requestTransaction(function*(){
|
||||
it('yield throws if request is unknown', function (done) {
|
||||
ob.requestTransaction(function *() {
|
||||
try {
|
||||
yield* this.setOperation();
|
||||
yield* this.setOperation()
|
||||
} catch (e) {
|
||||
expect(true).toEqual(true);
|
||||
done();
|
||||
return;
|
||||
expect(true).toEqual(true)
|
||||
done()
|
||||
return
|
||||
}
|
||||
expect("Expected an Error!").toEqual(true);
|
||||
done();
|
||||
});
|
||||
});
|
||||
expect('Expected an Error!').toEqual(true)
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it("sets and gets stateVector", function(done){
|
||||
ob.requestTransaction(function*(){
|
||||
var s1 = {user: "1", clock: 1};
|
||||
var s2 = {user: "2", clock: 3};
|
||||
yield* this.setState(s1);
|
||||
yield* this.setState(s2);
|
||||
var sv = yield* this.getStateVector();
|
||||
expect(sv).not.toBeUndefined();
|
||||
expect(sv).toEqual([s1, s2]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('sets and gets stateVector', function (done) {
|
||||
ob.requestTransaction(function *() {
|
||||
var s1 = {user: '1', clock: 1}
|
||||
var s2 = {user: '2', clock: 3}
|
||||
yield* this.setState(s1)
|
||||
yield* this.setState(s2)
|
||||
var sv = yield* this.getStateVector()
|
||||
expect(sv).not.toBeUndefined()
|
||||
expect(sv).toEqual([s1, s2])
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it("gets stateSet", function(done){
|
||||
ob.requestTransaction(function*(){
|
||||
var s1 = {user: "1", clock: 1};
|
||||
var s2 = {user: "2", clock: 3};
|
||||
yield* this.setState(s1);
|
||||
yield* this.setState(s2);
|
||||
var sv = yield* this.getStateSet();
|
||||
expect(sv).not.toBeUndefined();
|
||||
it('gets stateSet', function (done) {
|
||||
ob.requestTransaction(function *() {
|
||||
var s1 = {user: '1', clock: 1}
|
||||
var s2 = {user: '2', clock: 3}
|
||||
yield* this.setState(s1)
|
||||
yield* this.setState(s2)
|
||||
var sv = yield* this.getStateSet()
|
||||
expect(sv).not.toBeUndefined()
|
||||
expect(sv).toEqual({
|
||||
"1": 1,
|
||||
"2": 3
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
'1': 1,
|
||||
'2': 3
|
||||
})
|
||||
done()
|
||||
})
|
||||
})
|
||||
|
||||
it("getOperations returns operations (no parameters)", function(done){
|
||||
ob.requestTransaction(function*(){
|
||||
var s1 = {user: "1", clock: 55};
|
||||
yield* this.setState(s1);
|
||||
it('getOperations returns operations (no parameters)', function (done) {
|
||||
ob.requestTransaction(function *() {
|
||||
var s1 = {user: '1', clock: 55}
|
||||
yield* this.setState(s1)
|
||||
var op1 = yield* this.setOperation({
|
||||
"id": ["1", 0],
|
||||
"stuff": true
|
||||
});
|
||||
'id': ['1', 0],
|
||||
'stuff': true
|
||||
})
|
||||
var op2 = yield* this.setOperation({
|
||||
"id": ["1", 3],
|
||||
"stuff": true
|
||||
});
|
||||
var ops = yield* this.getOperations();
|
||||
expect(ops.length).toBeGreaterThan(1);
|
||||
expect(ops[0]).toEqual(op1);
|
||||
expect(ops[1]).toEqual(op2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
afterAll(function(done){
|
||||
ob.requestTransaction(function*(){
|
||||
yield* ob.removeDatabase();
|
||||
ob = null;
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
'id': ['1', 3],
|
||||
'stuff': true
|
||||
})
|
||||
var ops = yield* this.getOperations()
|
||||
expect(ops.length).toBeGreaterThan(1)
|
||||
expect(ops[0]).toEqual(op1)
|
||||
expect(ops[1]).toEqual(op2)
|
||||
done()
|
||||
})
|
||||
})
|
||||
afterAll(function (done) {
|
||||
ob.requestTransaction(function *() {
|
||||
yield* ob.removeDatabase()
|
||||
ob = null
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,155 +1,144 @@
|
||||
|
||||
type State = {
|
||||
user: string,
|
||||
clock: number
|
||||
};
|
||||
|
||||
/* global Struct, RBTree, Y */
|
||||
|
||||
function copyObject (o) {
|
||||
var c = {};
|
||||
var c = {}
|
||||
for (var key in o) {
|
||||
c[key] = o[key];
|
||||
c[key] = o[key]
|
||||
}
|
||||
return c;
|
||||
return c
|
||||
}
|
||||
|
||||
type StateVector = Array<State>;
|
||||
type StateSet = Object;
|
||||
Y.Memory = (function () { // eslint-disable-line no-unused-vars
|
||||
class Transaction extends AbstractTransaction { // eslint-disable-line
|
||||
|
||||
Y.Memory = (function(){ //eslint-disable-line no-unused-vars
|
||||
class Transaction extends AbstractTransaction { //eslint-disable-line
|
||||
ss: StateSet;
|
||||
os: RBTree;
|
||||
store: OperationStore;
|
||||
|
||||
constructor (store : OperationStore) {
|
||||
super(store);
|
||||
this.ss = store.ss;
|
||||
this.os = store.os;
|
||||
constructor (store) {
|
||||
super(store)
|
||||
this.ss = store.ss
|
||||
this.os = store.os
|
||||
}
|
||||
*setOperation (op) {
|
||||
* setOperation (op) { // eslint-disable-line
|
||||
// TODO: you can remove this step! probs..
|
||||
var n = this.os.findNode(op.id);
|
||||
n.val = op;
|
||||
return op;
|
||||
var n = this.os.findNode(op.id)
|
||||
n.val = op
|
||||
return op
|
||||
}
|
||||
*addOperation (op) {
|
||||
this.os.add(op);
|
||||
* addOperation (op) { // eslint-disable-line
|
||||
this.os.add(op)
|
||||
}
|
||||
*getOperation (id) {
|
||||
* getOperation (id) { // eslint-disable-line
|
||||
if (id == null) {
|
||||
throw new Error("You must define id!");
|
||||
throw new Error('You must define id!')
|
||||
}
|
||||
return this.os.find(id);
|
||||
return this.os.find(id)
|
||||
}
|
||||
*removeOperation (id) {
|
||||
this.os.delete(id);
|
||||
* removeOperation (id) { // eslint-disable-line
|
||||
this.os.delete(id)
|
||||
}
|
||||
*setState (state : State) : State {
|
||||
this.ss[state.user] = state.clock;
|
||||
* setState (state) { // eslint-disable-line
|
||||
this.ss[state.user] = state.clock
|
||||
}
|
||||
*getState (user : string) : State {
|
||||
var clock = this.ss[user];
|
||||
if (clock == null){
|
||||
clock = 0;
|
||||
* getState (user) { // eslint-disable-line
|
||||
var clock = this.ss[user]
|
||||
if (clock == null) {
|
||||
clock = 0
|
||||
}
|
||||
return {
|
||||
user: user,
|
||||
clock: clock
|
||||
};
|
||||
}
|
||||
}
|
||||
*getStateVector () : StateVector {
|
||||
var stateVector = [];
|
||||
* getStateVector () { // eslint-disable-line
|
||||
var stateVector = []
|
||||
for (var user in this.ss) {
|
||||
var clock = this.ss[user];
|
||||
var clock = this.ss[user]
|
||||
stateVector.push({
|
||||
user: user,
|
||||
clock: clock
|
||||
});
|
||||
})
|
||||
}
|
||||
return stateVector;
|
||||
return stateVector
|
||||
}
|
||||
*getStateSet () : StateSet {
|
||||
return this.ss;
|
||||
* getStateSet () { // eslint-disable-line
|
||||
return this.ss
|
||||
}
|
||||
*getOperations (startSS : StateSet) {
|
||||
* getOperations (startSS) {
|
||||
// TODO: use bounds here!
|
||||
if (startSS == null){
|
||||
startSS = {};
|
||||
if (startSS == null) {
|
||||
startSS = {}
|
||||
}
|
||||
var ops = [];
|
||||
var ops = []
|
||||
|
||||
var endSV : StateVector = yield* this.getStateVector();
|
||||
var endSV = yield* this.getStateVector()
|
||||
for (var endState of endSV) {
|
||||
var user = endState.user;
|
||||
if (user === "_") {
|
||||
continue;
|
||||
var user = endState.user
|
||||
if (user === '_') {
|
||||
continue
|
||||
}
|
||||
var startPos = startSS[user] || 0;
|
||||
var endPos = endState.clock;
|
||||
var startPos = startSS[user] || 0
|
||||
var endPos = endState.clock
|
||||
|
||||
this.os.iterate([user, startPos], [user, endPos], function(op){//eslint-disable-line
|
||||
ops.push(Struct[op.struct].encode(op));
|
||||
});
|
||||
this.os.iterate([user, startPos], [user, endPos], function (op) {// eslint-disable-line
|
||||
ops.push(Struct[op.struct].encode(op))
|
||||
})
|
||||
}
|
||||
var res = [];
|
||||
var res = []
|
||||
for (var op of ops) {
|
||||
res.push(yield* this.makeOperationReady.call(this, startSS, op));
|
||||
res.push(yield* this.makeOperationReady(startSS, op))
|
||||
}
|
||||
return res;
|
||||
return res
|
||||
}
|
||||
*makeOperationReady (ss, op) {
|
||||
* makeOperationReady (ss, op) {
|
||||
// instead of ss, you could use currSS (a ss that increments when you add an operation)
|
||||
var clock;
|
||||
var o = op;
|
||||
while (o.right != null){
|
||||
var clock
|
||||
var o = op
|
||||
while (o.right != null) {
|
||||
// while unknown, go to the right
|
||||
clock = ss[o.right[0]];
|
||||
clock = ss[o.right[0]]
|
||||
if (clock != null && o.right[1] < clock) {
|
||||
break;
|
||||
break
|
||||
}
|
||||
o = yield* this.getOperation(o.right);
|
||||
o = yield* this.getOperation(o.right)
|
||||
}
|
||||
op = copyObject(op);
|
||||
op.right = o.right;
|
||||
return op;
|
||||
op = copyObject(op)
|
||||
op.right = o.right
|
||||
return op
|
||||
}
|
||||
}
|
||||
class OperationStore extends AbstractOperationStore { //eslint-disable-line no-undef
|
||||
class OperationStore extends AbstractOperationStore { // eslint-disable-line no-undef
|
||||
constructor (y) {
|
||||
super(y);
|
||||
this.os = new RBTree();
|
||||
this.ss = {};
|
||||
this.waitingTransactions = [];
|
||||
this.transactionInProgress = false;
|
||||
super(y)
|
||||
this.os = new RBTree()
|
||||
this.ss = {}
|
||||
this.waitingTransactions = []
|
||||
this.transactionInProgress = false
|
||||
}
|
||||
requestTransaction (_makeGen : Function) {
|
||||
requestTransaction (_makeGen) {
|
||||
if (!this.transactionInProgress) {
|
||||
this.transactionInProgress = true;
|
||||
this.transactionInProgress = true
|
||||
setTimeout(() => {
|
||||
var makeGen = _makeGen;
|
||||
var makeGen = _makeGen
|
||||
while (makeGen != null) {
|
||||
var t = new Transaction(this);
|
||||
var gen = makeGen.call(t);
|
||||
var res = gen.next();
|
||||
while(!res.done){
|
||||
if (res.value === "transaction") {
|
||||
res = gen.next(t);
|
||||
var t = new Transaction(this)
|
||||
var gen = makeGen.call(t)
|
||||
var res = gen.next()
|
||||
while (!res.done) {
|
||||
if (res.value === 'transaction') {
|
||||
res = gen.next(t)
|
||||
} else {
|
||||
throw new Error("You must not yield this type. (Maybe you meant to use 'yield*'?)");
|
||||
throw new Error("You must not yield this type. (Maybe you meant to use 'yield*'?)")
|
||||
}
|
||||
}
|
||||
makeGen = this.waitingTransactions.shift();
|
||||
makeGen = this.waitingTransactions.shift()
|
||||
}
|
||||
this.transactionInProgress = false;
|
||||
}, 0);
|
||||
this.transactionInProgress = false
|
||||
}, 0)
|
||||
} else {
|
||||
this.waitingTransactions.push(_makeGen);
|
||||
this.waitingTransactions.push(_makeGen)
|
||||
}
|
||||
}
|
||||
*removeDatabase () {
|
||||
delete this.os;
|
||||
* removeDatabase () { // eslint-disable-line
|
||||
delete this.os
|
||||
}
|
||||
}
|
||||
return OperationStore;
|
||||
})();
|
||||
return OperationStore
|
||||
})()
|
||||
|
||||
@@ -1,367 +1,367 @@
|
||||
|
||||
/* global compareIds */
|
||||
function smaller (a, b) {
|
||||
return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1]);
|
||||
return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1])
|
||||
}
|
||||
|
||||
class N {
|
||||
// A created node is always red!
|
||||
constructor (val) {
|
||||
this.val = val;
|
||||
this.color = true;
|
||||
this._left = null;
|
||||
this._right = null;
|
||||
this._parent = null;
|
||||
this.val = val
|
||||
this.color = true
|
||||
this._left = null
|
||||
this._right = null
|
||||
this._parent = null
|
||||
if (val.id === null) {
|
||||
throw new Error("You must define id!");
|
||||
throw new Error('You must define id!')
|
||||
}
|
||||
}
|
||||
isRed () { return this.color; }
|
||||
isBlack () { return !this.color; }
|
||||
redden () { this.color = true; return this; }
|
||||
blacken () { this.color = false; return this; }
|
||||
isRed () { return this.color }
|
||||
isBlack () { return !this.color }
|
||||
redden () { this.color = true; return this }
|
||||
blacken () { this.color = false; return this }
|
||||
get grandparent () {
|
||||
return this.parent.parent;
|
||||
return this.parent.parent
|
||||
}
|
||||
get parent () {
|
||||
return this._parent;
|
||||
return this._parent
|
||||
}
|
||||
get sibling () {
|
||||
return (this === this.parent.left) ?
|
||||
this.parent.right : this.parent.left;
|
||||
this.parent.right : this.parent.left
|
||||
}
|
||||
get left () {
|
||||
return this._left;
|
||||
return this._left
|
||||
}
|
||||
get right () {
|
||||
return this._right;
|
||||
return this._right
|
||||
}
|
||||
set left (n) {
|
||||
if (n !== null) {
|
||||
n._parent = this;
|
||||
n._parent = this
|
||||
}
|
||||
this._left = n;
|
||||
this._left = n
|
||||
}
|
||||
set right (n) {
|
||||
if (n !== null) {
|
||||
n._parent = this;
|
||||
n._parent = this
|
||||
}
|
||||
this._right = n;
|
||||
this._right = n
|
||||
}
|
||||
rotateLeft (tree) {
|
||||
var parent = this.parent;
|
||||
var newParent = this.right;
|
||||
var newRight = this.right.left;
|
||||
newParent.left = this;
|
||||
this.right = newRight;
|
||||
var parent = this.parent
|
||||
var newParent = this.right
|
||||
var newRight = this.right.left
|
||||
newParent.left = this
|
||||
this.right = newRight
|
||||
if (parent === null) {
|
||||
tree.root = newParent;
|
||||
newParent._parent = null;
|
||||
tree.root = newParent
|
||||
newParent._parent = null
|
||||
} else if (parent.left === this) {
|
||||
parent.left = newParent;
|
||||
parent.left = newParent
|
||||
} else if (parent.right === this) {
|
||||
parent.right = newParent;
|
||||
parent.right = newParent
|
||||
} else {
|
||||
throw new Error("The elements are wrongly connected!");
|
||||
throw new Error('The elements are wrongly connected!')
|
||||
}
|
||||
}
|
||||
next () {
|
||||
if ( this.right !== null ) {
|
||||
if (this.right !== null) {
|
||||
// search the most left node in the right tree
|
||||
var o = this.right;
|
||||
var o = this.right
|
||||
while (o.left !== null) {
|
||||
o = o.left;
|
||||
o = o.left
|
||||
}
|
||||
return o;
|
||||
return o
|
||||
} else {
|
||||
var p = this;
|
||||
var p = this
|
||||
while (p.parent !== null && p !== p.parent.left) {
|
||||
p = p.parent;
|
||||
p = p.parent
|
||||
}
|
||||
return p.parent;
|
||||
return p.parent
|
||||
}
|
||||
}
|
||||
rotateRight (tree) {
|
||||
var parent = this.parent;
|
||||
var newParent = this.left;
|
||||
var newLeft = this.left.right;
|
||||
newParent.right = this;
|
||||
this.left = newLeft;
|
||||
var parent = this.parent
|
||||
var newParent = this.left
|
||||
var newLeft = this.left.right
|
||||
newParent.right = this
|
||||
this.left = newLeft
|
||||
if (parent === null) {
|
||||
tree.root = newParent;
|
||||
newParent._parent = null;
|
||||
tree.root = newParent
|
||||
newParent._parent = null
|
||||
} else if (parent.left === this) {
|
||||
parent.left = newParent;
|
||||
parent.left = newParent
|
||||
} else if (parent.right === this) {
|
||||
parent.right = newParent;
|
||||
parent.right = newParent
|
||||
} else {
|
||||
throw new Error("The elements are wrongly connected!");
|
||||
throw new Error('The elements are wrongly connected!')
|
||||
}
|
||||
}
|
||||
getUncle () {
|
||||
// we can assume that grandparent exists when this is called!
|
||||
if (this.parent === this.parent.parent.left) {
|
||||
return this.parent.parent.right;
|
||||
return this.parent.parent.right
|
||||
} else {
|
||||
return this.parent.parent.left;
|
||||
return this.parent.parent.left
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RBTree { //eslint-disable-line no-unused-vars
|
||||
class RBTree { // eslint-disable-line no-unused-vars
|
||||
constructor () {
|
||||
this.root = null;
|
||||
this.length = 0;
|
||||
this.root = null
|
||||
this.length = 0
|
||||
}
|
||||
findNodeWithLowerBound (from) {
|
||||
if (from === void 0) {
|
||||
throw new Error("You must define from!");
|
||||
throw new Error('You must define from!')
|
||||
}
|
||||
var o = this.root;
|
||||
var o = this.root
|
||||
if (o === null) {
|
||||
return false;
|
||||
return false
|
||||
} else {
|
||||
while (true) {
|
||||
if ((from === null || smaller(from, o.val.id)) && o.left !== null) {
|
||||
// o is included in the bound
|
||||
// try to find an element that is closer to the bound
|
||||
o = o.left;
|
||||
o = o.left
|
||||
} else if (from !== null && smaller(o.val.id, from)) {
|
||||
// o is not within the bound, maybe one of the right elements is..
|
||||
if (o.right !== null) {
|
||||
o = o.right;
|
||||
o = o.right
|
||||
} else {
|
||||
// there is no right element. Search for the next bigger element,
|
||||
// this should be within the bounds
|
||||
return o.next();
|
||||
return o.next()
|
||||
}
|
||||
} else {
|
||||
return o;
|
||||
return o
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
iterate (from, to, f) {
|
||||
var o = this.findNodeWithLowerBound(from);
|
||||
while ( o !== null && (to === null || smaller(o.val.id, to) || compareIds(o.val.id, to)) ) {
|
||||
f(o.val);
|
||||
o = o.next();
|
||||
var o = this.findNodeWithLowerBound(from)
|
||||
while (o !== null && (to === null || smaller(o.val.id, to) || compareIds(o.val.id, to))) {
|
||||
f(o.val)
|
||||
o = o.next()
|
||||
}
|
||||
return true;
|
||||
return true
|
||||
}
|
||||
find (id) {
|
||||
return this.findNode(id).val;
|
||||
return this.findNode(id).val
|
||||
}
|
||||
findNode (id) {
|
||||
if (id == null || id.constructor !== Array) {
|
||||
throw new Error("Expect id to be an array!");
|
||||
throw new Error('Expect id to be an array!')
|
||||
}
|
||||
var o = this.root;
|
||||
var o = this.root
|
||||
if (o === null) {
|
||||
return false;
|
||||
return false
|
||||
} else {
|
||||
while (true) {
|
||||
if (o === null) {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
if (smaller(id, o.val.id)) {
|
||||
o = o.left;
|
||||
o = o.left
|
||||
} else if (smaller(o.val.id, id)) {
|
||||
o = o.right;
|
||||
o = o.right
|
||||
} else {
|
||||
return o;
|
||||
return o
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
delete (id) {
|
||||
if (id == null || id.constructor !== Array) {
|
||||
throw new Error("id is expected to be an Array!");
|
||||
throw new Error('id is expected to be an Array!')
|
||||
}
|
||||
var d = this.findNode(id);
|
||||
var d = this.findNode(id)
|
||||
if (d == null) {
|
||||
throw new Error("Element does not exist!");
|
||||
throw new Error('Element does not exist!')
|
||||
}
|
||||
this.length--;
|
||||
this.length--
|
||||
if (d.left !== null && d.right !== null) {
|
||||
// switch d with the greates element in the left subtree.
|
||||
// o should have at most one child.
|
||||
var o = d.left;
|
||||
var o = d.left
|
||||
// find
|
||||
while (o.right !== null) {
|
||||
o = o.right;
|
||||
o = o.right
|
||||
}
|
||||
// switch
|
||||
d.val = o.val;
|
||||
d = o;
|
||||
d.val = o.val
|
||||
d = o
|
||||
}
|
||||
// d has at most one child
|
||||
// let n be the node that replaces d
|
||||
var isFakeChild;
|
||||
var child = d.left || d.right;
|
||||
if ( child === null) {
|
||||
isFakeChild = true;
|
||||
child = new N({id: 0});
|
||||
child.blacken();
|
||||
d.right = child;
|
||||
var isFakeChild
|
||||
var child = d.left || d.right
|
||||
if (child === null) {
|
||||
isFakeChild = true
|
||||
child = new N({id: 0})
|
||||
child.blacken()
|
||||
d.right = child
|
||||
} else {
|
||||
isFakeChild = false;
|
||||
isFakeChild = false
|
||||
}
|
||||
|
||||
if (d.parent === null) {
|
||||
if (!isFakeChild) {
|
||||
this.root = child;
|
||||
child.blacken();
|
||||
child._parent = null;
|
||||
this.root = child
|
||||
child.blacken()
|
||||
child._parent = null
|
||||
} else {
|
||||
this.root = null;
|
||||
this.root = null
|
||||
}
|
||||
return;
|
||||
return
|
||||
} else if (d.parent.left === d) {
|
||||
d.parent.left = child;
|
||||
d.parent.left = child
|
||||
} else if (d.parent.right === d) {
|
||||
d.parent.right = child;
|
||||
d.parent.right = child
|
||||
} else {
|
||||
throw new Error("Impossible!");
|
||||
throw new Error('Impossible!')
|
||||
}
|
||||
if ( d.isBlack() ) {
|
||||
if ( child.isRed() ) {
|
||||
child.blacken();
|
||||
if (d.isBlack()) {
|
||||
if (child.isRed()) {
|
||||
child.blacken()
|
||||
} else {
|
||||
this._fixDelete(child);
|
||||
this._fixDelete(child)
|
||||
}
|
||||
}
|
||||
this.root.blacken();
|
||||
this.root.blacken()
|
||||
if (isFakeChild) {
|
||||
if (child.parent.left === child) {
|
||||
child.parent.left = null;
|
||||
child.parent.left = null
|
||||
} else if (child.parent.right === child) {
|
||||
child.parent.right = null;
|
||||
child.parent.right = null
|
||||
} else {
|
||||
throw new Error("Impossible #3");
|
||||
throw new Error('Impossible #3')
|
||||
}
|
||||
}
|
||||
}
|
||||
_fixDelete (n) {
|
||||
function isBlack (node) {
|
||||
return node !== null ? node.isBlack() : true;
|
||||
return node !== null ? node.isBlack() : true
|
||||
}
|
||||
function isRed(node) {
|
||||
return node !== null ? node.isRed() : false;
|
||||
function isRed (node) {
|
||||
return node !== null ? node.isRed() : false
|
||||
}
|
||||
if (n.parent === null) {
|
||||
// this can only be called after the first iteration of fixDelete.
|
||||
return;
|
||||
return
|
||||
}
|
||||
// d was already replaced by the child
|
||||
// d is not the root
|
||||
// d and child are black
|
||||
var sibling = n.sibling;
|
||||
var sibling = n.sibling
|
||||
if (isRed(sibling)) {
|
||||
// make sibling the grandfather
|
||||
n.parent.redden();
|
||||
sibling.blacken();
|
||||
n.parent.redden()
|
||||
sibling.blacken()
|
||||
if (n === n.parent.left) {
|
||||
n.parent.rotateLeft(this);
|
||||
n.parent.rotateLeft(this)
|
||||
} else if (n === n.parent.right) {
|
||||
n.parent.rotateRight(this);
|
||||
n.parent.rotateRight(this)
|
||||
} else {
|
||||
throw new Error("Impossible #2");
|
||||
throw new Error('Impossible #2')
|
||||
}
|
||||
sibling = n.sibling;
|
||||
sibling = n.sibling
|
||||
}
|
||||
// parent, sibling, and children of n are black
|
||||
if ( n.parent.isBlack() &&
|
||||
sibling.isBlack() &&
|
||||
isBlack(sibling.left) &&
|
||||
isBlack(sibling.right)
|
||||
if (n.parent.isBlack() &&
|
||||
sibling.isBlack() &&
|
||||
isBlack(sibling.left) &&
|
||||
isBlack(sibling.right)
|
||||
) {
|
||||
sibling.redden();
|
||||
this._fixDelete(n.parent);
|
||||
} else if ( n.parent.isRed() &&
|
||||
sibling.isBlack() &&
|
||||
isBlack(sibling.left) &&
|
||||
isBlack(sibling.right)
|
||||
sibling.redden()
|
||||
this._fixDelete(n.parent)
|
||||
} else if (n.parent.isRed() &&
|
||||
sibling.isBlack() &&
|
||||
isBlack(sibling.left) &&
|
||||
isBlack(sibling.right)
|
||||
) {
|
||||
sibling.redden();
|
||||
n.parent.blacken();
|
||||
sibling.redden()
|
||||
n.parent.blacken()
|
||||
} else {
|
||||
if ( n === n.parent.left &&
|
||||
sibling.isBlack() &&
|
||||
isRed(sibling.left) &&
|
||||
isBlack(sibling.right)
|
||||
if (n === n.parent.left &&
|
||||
sibling.isBlack() &&
|
||||
isRed(sibling.left) &&
|
||||
isBlack(sibling.right)
|
||||
) {
|
||||
sibling.redden();
|
||||
sibling.left.blacken();
|
||||
sibling.rotateRight(this);
|
||||
sibling = n.sibling;
|
||||
} else if ( n === n.parent.right &&
|
||||
sibling.isBlack() &&
|
||||
isRed(sibling.right) &&
|
||||
isBlack(sibling.left)
|
||||
sibling.redden()
|
||||
sibling.left.blacken()
|
||||
sibling.rotateRight(this)
|
||||
sibling = n.sibling
|
||||
} else if (n === n.parent.right &&
|
||||
sibling.isBlack() &&
|
||||
isRed(sibling.right) &&
|
||||
isBlack(sibling.left)
|
||||
) {
|
||||
sibling.redden();
|
||||
sibling.right.blacken();
|
||||
sibling.rotateLeft(this);
|
||||
sibling = n.sibling;
|
||||
sibling.redden()
|
||||
sibling.right.blacken()
|
||||
sibling.rotateLeft(this)
|
||||
sibling = n.sibling
|
||||
}
|
||||
sibling.color = n.parent.color;
|
||||
n.parent.blacken();
|
||||
sibling.color = n.parent.color
|
||||
n.parent.blacken()
|
||||
if (n === n.parent.left) {
|
||||
sibling.right.blacken();
|
||||
n.parent.rotateLeft(this);
|
||||
sibling.right.blacken()
|
||||
n.parent.rotateLeft(this)
|
||||
} else {
|
||||
sibling.left.blacken();
|
||||
n.parent.rotateRight(this);
|
||||
sibling.left.blacken()
|
||||
n.parent.rotateRight(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
add (v) {
|
||||
if (v == null || v.id == null || v.id.constructor !== Array) {
|
||||
throw new Error("v is expected to have an id property which is an Array!");
|
||||
throw new Error('v is expected to have an id property which is an Array!')
|
||||
}
|
||||
var node = new N(v);
|
||||
var node = new N(v)
|
||||
if (this.root !== null) {
|
||||
var p = this.root; // p abbrev. parent
|
||||
var p = this.root // p abbrev. parent
|
||||
while (true) {
|
||||
if (smaller(node.val.id, p.val.id)) {
|
||||
if (p.left === null) {
|
||||
p.left = node;
|
||||
break;
|
||||
p.left = node
|
||||
break
|
||||
} else {
|
||||
p = p.left;
|
||||
p = p.left
|
||||
}
|
||||
} else if (smaller(p.val.id, node.val.id)) {
|
||||
if (p.right === null) {
|
||||
p.right = node;
|
||||
break;
|
||||
p.right = node
|
||||
break
|
||||
} else {
|
||||
p = p.right;
|
||||
p = p.right
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
return false
|
||||
}
|
||||
}
|
||||
this._fixInsert(node);
|
||||
this._fixInsert(node)
|
||||
} else {
|
||||
this.root = node;
|
||||
this.root = node
|
||||
}
|
||||
this.length++;
|
||||
this.root.blacken();
|
||||
this.length++
|
||||
this.root.blacken()
|
||||
}
|
||||
_fixInsert (n) {
|
||||
if (n.parent === null) {
|
||||
n.blacken();
|
||||
return;
|
||||
n.blacken()
|
||||
return
|
||||
} else if (n.parent.isBlack()) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
var uncle = n.getUncle();
|
||||
var uncle = n.getUncle()
|
||||
if (uncle !== null && uncle.isRed()) {
|
||||
// Note: parent: red, uncle: red
|
||||
n.parent.blacken();
|
||||
uncle.blacken();
|
||||
n.grandparent.redden();
|
||||
this._fixInsert(n.grandparent);
|
||||
n.parent.blacken()
|
||||
uncle.blacken()
|
||||
n.grandparent.redden()
|
||||
this._fixInsert(n.grandparent)
|
||||
} else {
|
||||
// Note: parent: red, uncle: black or null
|
||||
// Now we transform the tree in such a way that
|
||||
@@ -370,30 +370,28 @@ class RBTree { //eslint-disable-line no-unused-vars
|
||||
// and grandparent.left.left.isRed
|
||||
// 2) grandparent.right.isRed
|
||||
// and grandparent.right.right.isRed
|
||||
if (n === n.parent.right
|
||||
&& n.parent === n.grandparent.left) {
|
||||
n.parent.rotateLeft(this);
|
||||
// Since we rotated and want to use the previous
|
||||
// cases, we need to set n in such a way that
|
||||
// n.parent.isRed again
|
||||
n = n.left;
|
||||
} else if (n === n.parent.left
|
||||
&& n.parent === n.grandparent.right) {
|
||||
n.parent.rotateRight(this);
|
||||
// see above
|
||||
n = n.right;
|
||||
if (n === n.parent.right && n.parent === n.grandparent.left) {
|
||||
n.parent.rotateLeft(this)
|
||||
// Since we rotated and want to use the previous
|
||||
// cases, we need to set n in such a way that
|
||||
// n.parent.isRed again
|
||||
n = n.left
|
||||
} else if (n === n.parent.left && n.parent === n.grandparent.right) {
|
||||
n.parent.rotateRight(this)
|
||||
// see above
|
||||
n = n.right
|
||||
}
|
||||
// Case 1) or 2) hold from here on.
|
||||
// Now traverse grandparent, make parent a black node
|
||||
// on the highest level which holds two red nodes.
|
||||
n.parent.blacken();
|
||||
n.grandparent.redden();
|
||||
n.parent.blacken()
|
||||
n.grandparent.redden()
|
||||
if (n === n.parent.left) {
|
||||
// Case 1
|
||||
n.grandparent.rotateRight(this);
|
||||
n.grandparent.rotateRight(this)
|
||||
} else {
|
||||
// Case 2
|
||||
n.grandparent.rotateLeft(this);
|
||||
n.grandparent.rotateLeft(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,209 +1,209 @@
|
||||
/* @flow */
|
||||
/*eslint-env browser,jasmine,console */
|
||||
/* global RBTree, smaller, compareIds */
|
||||
/* eslint-env browser,jasmine,console */
|
||||
|
||||
var numberOfRBTreeTests = 1000;
|
||||
var numberOfRBTreeTests = 1000
|
||||
|
||||
function itRedNodesDoNotHaveBlackChildren (tree) {
|
||||
it("Red nodes do not have black children", function(){
|
||||
it('Red nodes do not have black children', function () {
|
||||
function traverse (n) {
|
||||
if (n == null) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
if (n.isRed()) {
|
||||
if (n.left != null) {
|
||||
expect(n.left.isRed()).not.toBeTruthy();
|
||||
expect(n.left.isRed()).not.toBeTruthy()
|
||||
}
|
||||
if (n.right != null) {
|
||||
expect(n.right.isRed()).not.toBeTruthy();
|
||||
expect(n.right.isRed()).not.toBeTruthy()
|
||||
}
|
||||
}
|
||||
traverse(n.left);
|
||||
traverse(n.right);
|
||||
traverse(n.left)
|
||||
traverse(n.right)
|
||||
}
|
||||
traverse(tree.root);
|
||||
});
|
||||
traverse(tree.root)
|
||||
})
|
||||
}
|
||||
|
||||
function itBlackHeightOfSubTreesAreEqual (tree){
|
||||
it("Black-height of sub-trees are equal", function(){
|
||||
function itBlackHeightOfSubTreesAreEqual (tree) {
|
||||
it('Black-height of sub-trees are equal', function () {
|
||||
function traverse (n) {
|
||||
if (n == null) {
|
||||
return 0;
|
||||
return 0
|
||||
}
|
||||
var sub1 = traverse(n.left);
|
||||
var sub2 = traverse(n.right);
|
||||
expect(sub1).toEqual(sub2);
|
||||
if(n.isRed()) {
|
||||
return sub1;
|
||||
var sub1 = traverse(n.left)
|
||||
var sub2 = traverse(n.right)
|
||||
expect(sub1).toEqual(sub2)
|
||||
if (n.isRed()) {
|
||||
return sub1
|
||||
} else {
|
||||
return sub1 + 1;
|
||||
return sub1 + 1
|
||||
}
|
||||
}
|
||||
traverse(tree.root);
|
||||
});
|
||||
traverse(tree.root)
|
||||
})
|
||||
}
|
||||
|
||||
function itRootNodeIsBlack(tree) {
|
||||
it("root node is black", function(){
|
||||
expect(tree.root == null || tree.root.isBlack()).toBeTruthy();
|
||||
});
|
||||
function itRootNodeIsBlack (tree) {
|
||||
it('root node is black', function () {
|
||||
expect(tree.root == null || tree.root.isBlack()).toBeTruthy()
|
||||
})
|
||||
}
|
||||
|
||||
describe("RedBlack Tree", function(){
|
||||
beforeEach(function(){
|
||||
this.tree = new RBTree();
|
||||
});
|
||||
it("can add&retrieve 5 elements", function(){
|
||||
this.tree.add({val: "four", id: [4]});
|
||||
this.tree.add({val: "one", id: [1]});
|
||||
this.tree.add({val: "three", id: [3]});
|
||||
this.tree.add({val: "two", id: [2]});
|
||||
this.tree.add({val: "five", id: [5]});
|
||||
expect(this.tree.find([1]).val).toEqual("one");
|
||||
expect(this.tree.find([2]).val).toEqual("two");
|
||||
expect(this.tree.find([3]).val).toEqual("three");
|
||||
expect(this.tree.find([4]).val).toEqual("four");
|
||||
expect(this.tree.find([5]).val).toEqual("five");
|
||||
});
|
||||
describe('RedBlack Tree', function () {
|
||||
beforeEach(function () {
|
||||
this.tree = new RBTree()
|
||||
})
|
||||
it('can add&retrieve 5 elements', function () {
|
||||
this.tree.add({val: 'four', id: [4]})
|
||||
this.tree.add({val: 'one', id: [1]})
|
||||
this.tree.add({val: 'three', id: [3]})
|
||||
this.tree.add({val: 'two', id: [2]})
|
||||
this.tree.add({val: 'five', id: [5]})
|
||||
expect(this.tree.find([1]).val).toEqual('one')
|
||||
expect(this.tree.find([2]).val).toEqual('two')
|
||||
expect(this.tree.find([3]).val).toEqual('three')
|
||||
expect(this.tree.find([4]).val).toEqual('four')
|
||||
expect(this.tree.find([5]).val).toEqual('five')
|
||||
})
|
||||
|
||||
it("5 elements do not exist anymore after deleting them", function(){
|
||||
this.tree.add({val: "four", id: [4]});
|
||||
this.tree.add({val: "one", id: [1]});
|
||||
this.tree.add({val: "three", id: [3]});
|
||||
this.tree.add({val: "two", id: [2]});
|
||||
this.tree.add({val: "five", id: [5]});
|
||||
this.tree.delete([4]);
|
||||
expect(this.tree.find([4])).not.toBeTruthy();
|
||||
this.tree.delete([3]);
|
||||
expect(this.tree.find([3])).not.toBeTruthy();
|
||||
this.tree.delete([2]);
|
||||
expect(this.tree.find([2])).not.toBeTruthy();
|
||||
this.tree.delete([1]);
|
||||
expect(this.tree.find([1])).not.toBeTruthy();
|
||||
this.tree.delete([5]);
|
||||
expect(this.tree.find([5])).not.toBeTruthy();
|
||||
});
|
||||
it('5 elements do not exist anymore after deleting them', function () {
|
||||
this.tree.add({val: 'four', id: [4]})
|
||||
this.tree.add({val: 'one', id: [1]})
|
||||
this.tree.add({val: 'three', id: [3]})
|
||||
this.tree.add({val: 'two', id: [2]})
|
||||
this.tree.add({val: 'five', id: [5]})
|
||||
this.tree.delete([4])
|
||||
expect(this.tree.find([4])).not.toBeTruthy()
|
||||
this.tree.delete([3])
|
||||
expect(this.tree.find([3])).not.toBeTruthy()
|
||||
this.tree.delete([2])
|
||||
expect(this.tree.find([2])).not.toBeTruthy()
|
||||
this.tree.delete([1])
|
||||
expect(this.tree.find([1])).not.toBeTruthy()
|
||||
this.tree.delete([5])
|
||||
expect(this.tree.find([5])).not.toBeTruthy()
|
||||
})
|
||||
|
||||
it("debug #1", function(){
|
||||
this.tree.add({id: [2]});
|
||||
this.tree.add({id: [0]});
|
||||
this.tree.delete([2]);
|
||||
this.tree.add({id: [1]});
|
||||
expect(this.tree.find([0])).not.toBeUndefined();
|
||||
expect(this.tree.find([1])).not.toBeUndefined();
|
||||
expect(this.tree.find([2])).toBeUndefined();
|
||||
});
|
||||
describe("debug #2", function(){
|
||||
var tree = new RBTree();
|
||||
tree.add({id: [8433]});
|
||||
tree.add({id: [12844]});
|
||||
tree.add({id: [1795]});
|
||||
tree.add({id: [30302]});
|
||||
tree.add({id: [64287]});
|
||||
tree.delete([8433]);
|
||||
tree.add({id: [28996]});
|
||||
tree.delete([64287]);
|
||||
tree.add({id: [22721]});
|
||||
it('debug #1', function () {
|
||||
this.tree.add({id: [2]})
|
||||
this.tree.add({id: [0]})
|
||||
this.tree.delete([2])
|
||||
this.tree.add({id: [1]})
|
||||
expect(this.tree.find([0])).not.toBeUndefined()
|
||||
expect(this.tree.find([1])).not.toBeUndefined()
|
||||
expect(this.tree.find([2])).toBeUndefined()
|
||||
})
|
||||
describe('debug #2', function () {
|
||||
var tree = new RBTree()
|
||||
tree.add({id: [8433]})
|
||||
tree.add({id: [12844]})
|
||||
tree.add({id: [1795]})
|
||||
tree.add({id: [30302]})
|
||||
tree.add({id: [64287]})
|
||||
tree.delete([8433])
|
||||
tree.add({id: [28996]})
|
||||
tree.delete([64287])
|
||||
tree.add({id: [22721]})
|
||||
|
||||
itRootNodeIsBlack(tree, []);
|
||||
itBlackHeightOfSubTreesAreEqual(tree, []);
|
||||
});
|
||||
itRootNodeIsBlack(tree, [])
|
||||
itBlackHeightOfSubTreesAreEqual(tree, [])
|
||||
})
|
||||
|
||||
describe(`After adding&deleting (0.8/0.2) ${numberOfRBTreeTests} times`, function () {
|
||||
var elements = [];
|
||||
var tree = new RBTree();
|
||||
for(var i = 0; i < numberOfRBTreeTests; i++) {
|
||||
var r = Math.random();
|
||||
var elements = []
|
||||
var tree = new RBTree()
|
||||
for (var i = 0; i < numberOfRBTreeTests; i++) {
|
||||
var r = Math.random()
|
||||
if (r < 0.8) {
|
||||
var obj = [Math.floor(Math.random() * numberOfRBTreeTests * 10000)];
|
||||
elements.push(obj);
|
||||
tree.add({id: obj});
|
||||
var obj = [Math.floor(Math.random() * numberOfRBTreeTests * 10000)]
|
||||
elements.push(obj)
|
||||
tree.add({id: obj})
|
||||
} else if (elements.length > 0) {
|
||||
var elemid = Math.floor(Math.random() * elements.length);
|
||||
var elem = elements[elemid];
|
||||
elements = elements.filter(function(e){return !compareIds(e,elem); }); //eslint-disable-line
|
||||
tree.delete(elem);
|
||||
var elemid = Math.floor(Math.random() * elements.length)
|
||||
var elem = elements[elemid]
|
||||
elements = elements.filter(function (e) {return !compareIds(e, elem); }); // eslint-disable-line
|
||||
tree.delete(elem)
|
||||
}
|
||||
}
|
||||
itRootNodeIsBlack(tree);
|
||||
itRootNodeIsBlack(tree)
|
||||
|
||||
it("can find every object", function(){
|
||||
for(var id of elements) {
|
||||
expect(tree.find(id).id).toEqual(id);
|
||||
it('can find every object', function () {
|
||||
for (var id of elements) {
|
||||
expect(tree.find(id).id).toEqual(id)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it("can find every object with lower bound search", function(){
|
||||
for(var id of elements) {
|
||||
expect(tree.findNodeWithLowerBound(id).val.id).toEqual(id);
|
||||
it('can find every object with lower bound search', function () {
|
||||
for (var id of elements) {
|
||||
expect(tree.findNodeWithLowerBound(id).val.id).toEqual(id)
|
||||
}
|
||||
});
|
||||
itRedNodesDoNotHaveBlackChildren(tree);
|
||||
})
|
||||
itRedNodesDoNotHaveBlackChildren(tree)
|
||||
|
||||
itBlackHeightOfSubTreesAreEqual(tree);
|
||||
itBlackHeightOfSubTreesAreEqual(tree)
|
||||
|
||||
it("iterating over a tree with lower bound yields the right amount of results", function(){
|
||||
var lowerBound = elements[Math.floor(Math.random() * elements.length)];
|
||||
var expectedResults = elements.filter(function(e, pos){
|
||||
return (smaller(lowerBound, e) || compareIds(e, lowerBound)) && elements.indexOf(e) === pos;
|
||||
}).length;
|
||||
it('iterating over a tree with lower bound yields the right amount of results', function () {
|
||||
var lowerBound = elements[Math.floor(Math.random() * elements.length)]
|
||||
var expectedResults = elements.filter(function (e, pos) {
|
||||
return (smaller(lowerBound, e) || compareIds(e, lowerBound)) && elements.indexOf(e) === pos
|
||||
}).length
|
||||
|
||||
var actualResults = 0;
|
||||
tree.iterate(lowerBound, null, function(val){
|
||||
expect(val).not.toBeUndefined();
|
||||
actualResults++;
|
||||
});
|
||||
expect(expectedResults).toEqual(actualResults);
|
||||
});
|
||||
var actualResults = 0
|
||||
tree.iterate(lowerBound, null, function (val) {
|
||||
expect(val).not.toBeUndefined()
|
||||
actualResults++
|
||||
})
|
||||
expect(expectedResults).toEqual(actualResults)
|
||||
})
|
||||
|
||||
it("iterating over a tree without bounds yield the right amount of results", function(){
|
||||
var lowerBound = null;
|
||||
var expectedResults = elements.filter(function(e, pos){
|
||||
return elements.indexOf(e) === pos;
|
||||
}).length;
|
||||
var actualResults = 0;
|
||||
tree.iterate(lowerBound, null, function(val){
|
||||
expect(val).not.toBeUndefined();
|
||||
actualResults++;
|
||||
});
|
||||
expect(expectedResults).toEqual(actualResults);
|
||||
});
|
||||
it('iterating over a tree without bounds yield the right amount of results', function () {
|
||||
var lowerBound = null
|
||||
var expectedResults = elements.filter(function (e, pos) {
|
||||
return elements.indexOf(e) === pos
|
||||
}).length
|
||||
var actualResults = 0
|
||||
tree.iterate(lowerBound, null, function (val) {
|
||||
expect(val).not.toBeUndefined()
|
||||
actualResults++
|
||||
})
|
||||
expect(expectedResults).toEqual(actualResults)
|
||||
})
|
||||
|
||||
it("iterating over a tree with upper bound yields the right amount of results", function(){
|
||||
var upperBound = elements[Math.floor(Math.random() * elements.length)];
|
||||
var expectedResults = elements.filter(function(e, pos){
|
||||
return (smaller(e, upperBound) || compareIds(e, upperBound)) && elements.indexOf(e) === pos;
|
||||
}).length;
|
||||
it('iterating over a tree with upper bound yields the right amount of results', function () {
|
||||
var upperBound = elements[Math.floor(Math.random() * elements.length)]
|
||||
var expectedResults = elements.filter(function (e, pos) {
|
||||
return (smaller(e, upperBound) || compareIds(e, upperBound)) && elements.indexOf(e) === pos
|
||||
}).length
|
||||
|
||||
var actualResults = 0;
|
||||
tree.iterate(null, upperBound, function(val){
|
||||
expect(val).not.toBeUndefined();
|
||||
actualResults++;
|
||||
});
|
||||
expect(expectedResults).toEqual(actualResults);
|
||||
});
|
||||
var actualResults = 0
|
||||
tree.iterate(null, upperBound, function (val) {
|
||||
expect(val).not.toBeUndefined()
|
||||
actualResults++
|
||||
})
|
||||
expect(expectedResults).toEqual(actualResults)
|
||||
})
|
||||
|
||||
it("iterating over a tree with upper and lower bounds yield the right amount of results", function(){
|
||||
var b1 = elements[Math.floor(Math.random() * elements.length)];
|
||||
var b2 = elements[Math.floor(Math.random() * elements.length)];
|
||||
var upperBound, lowerBound;
|
||||
it('iterating over a tree with upper and lower bounds yield the right amount of results', function () {
|
||||
var b1 = elements[Math.floor(Math.random() * elements.length)]
|
||||
var b2 = elements[Math.floor(Math.random() * elements.length)]
|
||||
var upperBound, lowerBound
|
||||
if (smaller(b1, b2)) {
|
||||
lowerBound = b1;
|
||||
upperBound = b2;
|
||||
lowerBound = b1
|
||||
upperBound = b2
|
||||
} else {
|
||||
lowerBound = b2;
|
||||
upperBound = b1;
|
||||
lowerBound = b2
|
||||
upperBound = b1
|
||||
}
|
||||
var expectedResults = elements.filter(function(e, pos){
|
||||
return (smaller(lowerBound, e) || compareIds(e, lowerBound))
|
||||
&& (smaller(e, upperBound) || compareIds(e, upperBound)) && elements.indexOf(e) === pos;
|
||||
}).length;
|
||||
var actualResults = 0;
|
||||
tree.iterate(lowerBound, upperBound, function(val){
|
||||
expect(val).not.toBeUndefined();
|
||||
actualResults++;
|
||||
});
|
||||
expect(expectedResults).toEqual(actualResults);
|
||||
});
|
||||
});
|
||||
});
|
||||
var expectedResults = elements.filter(function (e, pos) {
|
||||
return (smaller(lowerBound, e) || compareIds(e, lowerBound)) &&
|
||||
(smaller(e, upperBound) || compareIds(e, upperBound)) && elements.indexOf(e) === pos
|
||||
}).length
|
||||
var actualResults = 0
|
||||
tree.iterate(lowerBound, upperBound, function (val) {
|
||||
expect(val).not.toBeUndefined()
|
||||
actualResults++
|
||||
})
|
||||
expect(expectedResults).toEqual(actualResults)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user