finished & tested DeleteStore
This commit is contained in:
		
							parent
							
								
									420821be31
								
							
						
					
					
						commit
						5e0d602e12
					
				@ -63,11 +63,12 @@ var options = minimist(process.argv.slice(2), {
 | 
			
		||||
    testfiles: 'src/**/*.js'
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
var yfiles = polyfills.concat(['src/y.js', 'src/Connector.js', 'src/OperationStore.js', 'src/Struct.js', 'src/Utils.js',
 | 
			
		||||
    'src/OperationStores/RedBlackTree.js', 'src/Memory.js', 'src/**/*.js'])
 | 
			
		||||
 | 
			
		||||
var files = {
 | 
			
		||||
  y: polyfills.concat(['src/y.js', 'src/Connector.js', 'src/OperationStore.js', 'src/Struct.js', 'src/Utils.js',
 | 
			
		||||
    'src/OperationStores/RedBlackTree.js', 'src/**/*.js', '!src/**/*.spec.js']),
 | 
			
		||||
  test: polyfills.concat([options.testfiles]),
 | 
			
		||||
  y: yfiles.concat(['!src/**/*.spec.js']),
 | 
			
		||||
  test: yfiles.concat([options.testfiles]),
 | 
			
		||||
  build_test: ['build_test/y.js']
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -51,30 +51,63 @@ async function applyRandomTransactions (users, objects, transactions, numberOfTr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function compareAllUsers(users){//eslint-disable-line
 | 
			
		||||
  var s1, s2
 | 
			
		||||
  var s1, s2, ds1, ds2, allDels1, allDels2
 | 
			
		||||
  var db1 = []
 | 
			
		||||
  function * t1 () {
 | 
			
		||||
    s1 = yield* this.getStateSet()
 | 
			
		||||
    ds1 = yield* this.getDeletionSet()
 | 
			
		||||
    allDels1 = []
 | 
			
		||||
    yield* this.ds.iterate(null, null, function (d) {
 | 
			
		||||
      allDels1.push(d)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  function * t2 () {
 | 
			
		||||
    s2 = yield* this.getStateSet()
 | 
			
		||||
    ds2 = yield* this.getDeletionSet()
 | 
			
		||||
    allDels2 = []
 | 
			
		||||
    yield* this.ds.iterate(null, null, function (d) {
 | 
			
		||||
      allDels2.push(d)
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
  await users[0].connector.flushAll()
 | 
			
		||||
  for (var uid = 0; uid < users.length; uid++) {
 | 
			
		||||
    if (s1 == null) {
 | 
			
		||||
    var u = users[uid]
 | 
			
		||||
    // compare deleted ops against deleteStore
 | 
			
		||||
    u.db.os.iterate(null, null, function (o) {
 | 
			
		||||
      if (o.deleted === true) {
 | 
			
		||||
        expect(u.db.ds.isDeleted(o.id)).toBeTruthy()
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    // compare deleteStore against deleted ops
 | 
			
		||||
    u.db.requestTransaction(function * () {
 | 
			
		||||
      var ds = []
 | 
			
		||||
      u.db.ds.iterate(null, null, function (d) {
 | 
			
		||||
        ds.push(d)
 | 
			
		||||
      })
 | 
			
		||||
      for (var j in ds) {
 | 
			
		||||
        var d = ds[j]
 | 
			
		||||
        for (var i = 0; i < d.len; i++) {
 | 
			
		||||
          var o = yield* this.getOperation([d.id[0], d.id[1] + i])
 | 
			
		||||
          expect(o.deleted).toBeTruthy()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    // compare allDels tree
 | 
			
		||||
    await wait()
 | 
			
		||||
    if (s1 == null) {
 | 
			
		||||
      u.db.requestTransaction(t1)
 | 
			
		||||
      await wait()
 | 
			
		||||
      u.db.os.iterate(null, null, function(o){//eslint-disable-line
 | 
			
		||||
        db1.push(o)
 | 
			
		||||
      })
 | 
			
		||||
    } else {
 | 
			
		||||
      var u2 = users[uid]
 | 
			
		||||
      u2.db.requestTransaction(t2)
 | 
			
		||||
      u.db.requestTransaction(t2)
 | 
			
		||||
      await wait()
 | 
			
		||||
      expect(s1).toEqual(s2)
 | 
			
		||||
      expect(allDels1).toEqual(allDels2) // inner structure
 | 
			
		||||
      expect(ds1).toEqual(ds2) // exported structure
 | 
			
		||||
      var count = 0
 | 
			
		||||
      u2.db.os.iterate(null, null, function(o){//eslint-disable-line
 | 
			
		||||
      u.db.os.iterate(null, null, function(o){//eslint-disable-line
 | 
			
		||||
        expect(db1[count++]).toEqual(o)
 | 
			
		||||
      })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -166,6 +166,9 @@ class AbstractOperationStore { // eslint-disable-line no-unused-vars
 | 
			
		||||
          yield* Struct[op.struct].execute.call(this, op)
 | 
			
		||||
          yield* this.addOperation(op)
 | 
			
		||||
          yield* this.store.operationAdded(this, op)
 | 
			
		||||
          if (op.deleted === true) {
 | 
			
		||||
            this.ds.delete(op.id)
 | 
			
		||||
          }
 | 
			
		||||
          // find next operation to execute
 | 
			
		||||
          op = this.store.waitingOperations.find([op.id[0], state.clock])
 | 
			
		||||
          if (op != null) {
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
/* global Struct, RBTree, Y */
 | 
			
		||||
/* global Struct, RBTree, Y, compareIds */
 | 
			
		||||
 | 
			
		||||
function copyObject (o) {
 | 
			
		||||
  var c = {}
 | 
			
		||||
@ -8,9 +8,13 @@ function copyObject (o) {
 | 
			
		||||
  return c
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class DeletionStore { // eslint-disable-line
 | 
			
		||||
class DeleteStore extends RBTree { // eslint-disable-line
 | 
			
		||||
  constructor () {
 | 
			
		||||
    this.ds = {}
 | 
			
		||||
    super()
 | 
			
		||||
  }
 | 
			
		||||
  isDeleted (id) {
 | 
			
		||||
    var n = this.findNodeWithUpperBound(id)
 | 
			
		||||
    return n !== null && n.val.id[0] === id[0] && id[0] < n.val.id[0] + n.val.len
 | 
			
		||||
  }
 | 
			
		||||
  delete (id) {
 | 
			
		||||
    var n = this.findNodeWithUpperBound(id)
 | 
			
		||||
@ -18,43 +22,46 @@ class DeletionStore { // eslint-disable-line
 | 
			
		||||
      if (n.val.id[1] === id[1]) {
 | 
			
		||||
        // already deleted
 | 
			
		||||
        return
 | 
			
		||||
      } else if (n.val.id[1] + n.val.length === id[1]) {
 | 
			
		||||
      } else if (n.val.id[1] + n.val.len === id[1]) {
 | 
			
		||||
        // can extend existing deletion
 | 
			
		||||
        n.val.length++
 | 
			
		||||
        n.val.len++
 | 
			
		||||
      } else {
 | 
			
		||||
        // cannot extend left
 | 
			
		||||
        n = this.add({id: id, length: 1})
 | 
			
		||||
        n = this.add({id: id, len: 1})
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      // cannot extend left
 | 
			
		||||
      n = this.add({id: id, length: 1})
 | 
			
		||||
      n = this.add({id: id, len: 1})
 | 
			
		||||
    }
 | 
			
		||||
    // can extend right?
 | 
			
		||||
    var next = n.next()
 | 
			
		||||
    if compareIds([n.val.id[0], n.val.id[1] + n.val.length], next.val.id) {
 | 
			
		||||
      n.val.length += next.val.length
 | 
			
		||||
      this.delete(next.val.id)
 | 
			
		||||
    if (next !== null && compareIds([n.val.id[0], n.val.id[1] + n.val.len], next.val.id)) {
 | 
			
		||||
      n.val.len = n.val.len + next.val.len
 | 
			
		||||
      super.delete(next.val.id)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  // a DeleteSet (ds) describes all the deleted ops in the OS
 | 
			
		||||
  toDeleteSet () {
 | 
			
		||||
    var ds = {}
 | 
			
		||||
    this.iterate(null, null, function (n) {
 | 
			
		||||
      var user = n.val.id[0]
 | 
			
		||||
      var counter = n.val.id[1]
 | 
			
		||||
      var length = n.val.length
 | 
			
		||||
      var user = n.id[0]
 | 
			
		||||
      var counter = n.id[1]
 | 
			
		||||
      var len = n.len
 | 
			
		||||
      var dv = ds[user]
 | 
			
		||||
      if (dv === void 0) {
 | 
			
		||||
        dv = []
 | 
			
		||||
        ds[user] = dv
 | 
			
		||||
      }
 | 
			
		||||
      dv.push([counter, length])
 | 
			
		||||
      dv.push([counter, len])
 | 
			
		||||
    })
 | 
			
		||||
    return ds
 | 
			
		||||
  }
 | 
			
		||||
  // returns a set of deletions that need to be applied in order to get to
 | 
			
		||||
  // the state of the supplied ds
 | 
			
		||||
  getDeletions (ds) {
 | 
			
		||||
    var deletions = []
 | 
			
		||||
      function createDeletions (user, start, length) {
 | 
			
		||||
        for (var c = start; c < start + length; c++) {
 | 
			
		||||
    function createDeletions (user, start, len) {
 | 
			
		||||
      for (var c = start; c < start + len; c++) {
 | 
			
		||||
        deletions.push({
 | 
			
		||||
          target: [user, c],
 | 
			
		||||
          struct: 'Delete'
 | 
			
		||||
@ -78,18 +85,18 @@ class DeletionStore { // eslint-disable-line
 | 
			
		||||
        //  *)=> if d does not delete anything anymore, go to next d (continue)
 | 
			
		||||
        while (d != null) {
 | 
			
		||||
          var diff // describe the diff of length in 1) and 2)
 | 
			
		||||
            if (n.val.id[1] + n.val.length <= d[0]) {
 | 
			
		||||
          if (n.id[1] + n.len <= d[0]) {
 | 
			
		||||
            // 1)
 | 
			
		||||
            break
 | 
			
		||||
            } else if (d[0] < n.val.id[1]) {
 | 
			
		||||
          } else if (d[0] < n.id[1]) {
 | 
			
		||||
            // 2)
 | 
			
		||||
              // delete maximum the length of d
 | 
			
		||||
            // delete maximum the len of d
 | 
			
		||||
            // else delete as much as possible
 | 
			
		||||
              diff = Math.min(n.val.id[1]-d[0], d[1])
 | 
			
		||||
            diff = Math.min(n.id[1] - d[0], d[1])
 | 
			
		||||
            createDeletions(user, d[0], diff)
 | 
			
		||||
          } else {
 | 
			
		||||
            // 3)
 | 
			
		||||
              diff = n.val.id[1] + n.val.length - d[0] // never null (see 1)
 | 
			
		||||
            diff = n.id[1] + n.len - d[0] // never null (see 1)
 | 
			
		||||
          }
 | 
			
		||||
          if (d[1] <= diff) {
 | 
			
		||||
            // d doesn't delete anything anymore
 | 
			
		||||
@ -100,9 +107,11 @@ class DeletionStore { // eslint-disable-line
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
      for (; pos < dv.len; pos++) {
 | 
			
		||||
        d = dv[pos]
 | 
			
		||||
        createDeletions(user, d[0], d[1])
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
      this.iterater()
 | 
			
		||||
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -113,6 +122,16 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
 | 
			
		||||
      super(store)
 | 
			
		||||
      this.ss = store.ss
 | 
			
		||||
      this.os = store.os
 | 
			
		||||
      this.ds = store.ds
 | 
			
		||||
    }
 | 
			
		||||
    * getDeletionSet (id) {
 | 
			
		||||
      return this.ds.toDeleteSet(id)
 | 
			
		||||
    }
 | 
			
		||||
    * isDeleted (id) {
 | 
			
		||||
      return this.ds.isDeleted(id)
 | 
			
		||||
    }
 | 
			
		||||
    * getDeletions (ds) {
 | 
			
		||||
      return this.ds.getDeletions(ds)
 | 
			
		||||
    }
 | 
			
		||||
    * setOperation (op) { // eslint-disable-line
 | 
			
		||||
      // TODO: you can remove this step! probs..
 | 
			
		||||
@ -209,6 +228,10 @@ Y.Memory = (function () { // eslint-disable-line no-unused-vars
 | 
			
		||||
      this.ss = {}
 | 
			
		||||
      this.waitingTransactions = []
 | 
			
		||||
      this.transactionInProgress = false
 | 
			
		||||
      this.ds = new DeleteStore()
 | 
			
		||||
    }
 | 
			
		||||
    logTable () {
 | 
			
		||||
      this.os.logTable()
 | 
			
		||||
    }
 | 
			
		||||
    requestTransaction (_makeGen) {
 | 
			
		||||
      if (!this.transactionInProgress) {
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										23
									
								
								src/OperationStores/Memory.spec.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/OperationStores/Memory.spec.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
/* global DeleteStore */
 | 
			
		||||
/* eslint-env browser,jasmine,console */
 | 
			
		||||
 | 
			
		||||
describe('Memory', function () {
 | 
			
		||||
  describe('DeleteStore', function () {
 | 
			
		||||
    var ds
 | 
			
		||||
    beforeEach(function () {
 | 
			
		||||
      ds = new DeleteStore()
 | 
			
		||||
    })
 | 
			
		||||
    it('Deleted operation is deleted', function () {
 | 
			
		||||
      ds.delete(['u1', 10])
 | 
			
		||||
      expect(ds.isDeleted(['u1', 10])).toBeTruthy()
 | 
			
		||||
      expect(ds.toDeleteSet()).toBeTruthy({'u1': [10, 1]})
 | 
			
		||||
    })
 | 
			
		||||
    it('Deleted operation extends other deleted operation', function () {
 | 
			
		||||
      ds.delete(['u1', 10])
 | 
			
		||||
      ds.delete(['u1', 11])
 | 
			
		||||
      expect(ds.isDeleted(['u1', 10])).toBeTruthy()
 | 
			
		||||
      expect(ds.isDeleted(['u1', 11])).toBeTruthy()
 | 
			
		||||
      expect(ds.toDeleteSet()).toBeTruthy({'u1': [10, 2]})
 | 
			
		||||
    })
 | 
			
		||||
  })
 | 
			
		||||
})
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
/* global compareIds */
 | 
			
		||||
/* global compareIds, copyObject */
 | 
			
		||||
function smaller (a, b) {
 | 
			
		||||
  return a[0] < b[0] || (a[0] === b[0] && a[1] < b[1])
 | 
			
		||||
}
 | 
			
		||||
@ -192,6 +192,18 @@ class RBTree { // eslint-disable-line no-unused-vars
 | 
			
		||||
    }
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
  logTable (from = null, to = null) {
 | 
			
		||||
    var os = []
 | 
			
		||||
    this.iterate(from, to, function (o) {
 | 
			
		||||
      var o_ = copyObject(o)
 | 
			
		||||
      var id = o_.id
 | 
			
		||||
      delete o_.id
 | 
			
		||||
      o_['id[0]'] = id[0]
 | 
			
		||||
      o_['id[1]'] = id[1]
 | 
			
		||||
      os.push(o_)
 | 
			
		||||
    })
 | 
			
		||||
    console.table(os)
 | 
			
		||||
  }
 | 
			
		||||
  find (id) {
 | 
			
		||||
    return this.findNode(id).val
 | 
			
		||||
  }
 | 
			
		||||
@ -382,7 +394,7 @@ class RBTree { // eslint-disable-line no-unused-vars
 | 
			
		||||
            p = p.right
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          return false
 | 
			
		||||
          return null
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      this._fixInsert(node)
 | 
			
		||||
@ -391,6 +403,7 @@ class RBTree { // eslint-disable-line no-unused-vars
 | 
			
		||||
    }
 | 
			
		||||
    this.length++
 | 
			
		||||
    this.root.blacken()
 | 
			
		||||
    return node
 | 
			
		||||
  }
 | 
			
		||||
  _fixInsert (n) {
 | 
			
		||||
    if (n.parent === null) {
 | 
			
		||||
 | 
			
		||||
@ -32,6 +32,7 @@ var Struct = {
 | 
			
		||||
      if (!target.deleted) {
 | 
			
		||||
        target.deleted = true
 | 
			
		||||
        yield* this.setOperation(target)
 | 
			
		||||
        this.ds.delete(target.id)
 | 
			
		||||
        var t = this.store.initializedTypes[JSON.stringify(target.parent)]
 | 
			
		||||
        if (t != null) {
 | 
			
		||||
          yield* t._changed(this, copyObject(op))
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user