* starting flow integration

* found a bug in EventHelper
* reduce wait() calls
This commit is contained in:
Kevin Jahns 2015-11-26 00:46:02 +01:00
parent 940a44bb7c
commit bd9c3813fd
12 changed files with 200 additions and 75 deletions

12
.flowconfig Normal file
View File

@ -0,0 +1,12 @@
[ignore]
.*/node_modules/.*
.*/dist/.*
.*/build/.*
[include]
./src/
[libs]
./declarations/
[options]

View File

@ -1,3 +1,5 @@
// Place your settings in this file to overwrite default and user settings.
{
"standard.enable": true,
"javascript.validate.enable": false
}

26
declarations/Structs.js Normal file
View File

@ -0,0 +1,26 @@
/* @flow */
type UserId = string
type Id = [UserId, number]
/*
type Struct = {
id: Id,
left?: Id,
right?: Id,
target?: Id,
struct: 'Insert' | 'Delete'
}*/
type Struct = Insertion | Deletion
type Insertion = {
id: Id,
left: Id,
right: Id,
struct: 'Insert'
}
type Deletion = {
target: Id,
struct: 'Delete'
}

17
declarations/Y.js Normal file
View File

@ -0,0 +1,17 @@
/* @flow */
type YGlobal = {
utils: Object;
Struct: Object;
AbstractDatabase: any;
}
type YInstance = {
db: Object,
connector: Object,
root: Object
}
declare var YConcurrency_TestingMode : boolean
type Transaction<A> = Generator<any, A, any>

View File

@ -16,6 +16,7 @@
"ignore": [
"build/**",
"dist/**",
"declarations/**",
"./y.js",
"./y.js.map"
]

View File

@ -190,7 +190,7 @@ module.exports = function (Y) {
this.broadcastedHB = true
var db = this.y.db
var defer = Promise.defer()
this.syncStep2 = defer.promise
this.syncStep2 = defer.promise
db.requestTransaction(function * () {
yield* this.applyDeleteSet(m.deleteSet)
this.store.apply(m.os)

View File

@ -24,16 +24,11 @@ module.exports = function (Y) {
}
},
whenTransactionsFinished: function () {
var self = this
return new Promise (function (resolve) {
wait().then(function () {
var ps = []
for (var name in self.users) {
ps.push(self.users[name].y.db.whenTransactionsFinished())
}
Promise.all(ps).then(resolve)
})
})
var ps = []
for (var name in this.users) {
ps.push(this.users[name].y.db.whenTransactionsFinished())
}
return Promise.all(ps)
},
flushOne: function flushOne () {
var bufs = []
@ -59,15 +54,16 @@ module.exports = function (Y) {
function nextFlush () {
var c = globalRoom.flushOne()
if (c) {
while (c = globalRoom.flushOne()) {
while (c) {
c = globalRoom.flushOne()
}
globalRoom.whenTransactionsFinished().then(nextFlush)
globalRoom.whenTransactionsFinished().then(nextFlush)
} else {
setTimeout(function () {
var c = globalRoom.flushOne()
if (c) {
c.then(function () {
globalRoom.whenTransactionsFinished().then(nextFlush)
globalRoom.whenTransactionsFinished().then(nextFlush)
})
} else {
resolve()

View File

@ -1,6 +1,7 @@
/* @flow */
'use strict'
module.exports = function (Y) {
module.exports = function (Y /* : YGlobal */) {
/*
Partial definition of an OperationStore.
TODO: name it Database, operation store only holds operations.
@ -14,6 +15,28 @@ module.exports = function (Y) {
- destroy the database
*/
class AbstractDatabase {
/* ::
y: YInstance;
forwardAppliedOperations: boolean;
listenersById: Object;
listenersByIdExecuteNow: Array<Object>;
listenersByIdRequestPending: boolean;
initializedTypes: Object;
whenUserIdSetListener: ?Function;
waitingTransactions: Array<Transaction>;
transactionInProgress: boolean;
executeOrder: Array<Object>;
gc1: Array<Struct>;
gc2: Array<Struct>;
gcTimeout: number;
gcInterval: any;
garbageCollect: Function;
executeOrder: Array<any>; // for debugging only
userId: UserId;
opClock: number;
transactionsFinished: ?{promise: Promise, resolve: any};
transact: (x: ?Generator) => any;
*/
constructor (y, opts) {
this.y = y
// whether to broadcast all applied operations (insert & delete hook)
@ -51,7 +74,7 @@ module.exports = function (Y) {
return new Promise((resolve) => {
os.requestTransaction(function * () {
if (os.y.connector != null && os.y.connector.isSynced) {
for (var i in os.gc2) {
for (var i = 0; i < os.gc2.length; i++) {
var oid = os.gc2[i]
yield* this.garbageCollectOperation(oid)
}
@ -72,7 +95,7 @@ module.exports = function (Y) {
}
addToDebug () {
if (typeof YConcurrency_TestingMode !== 'undefined') {
var command = Array.prototype.map.call(arguments, function (s) {
var command /* :string */ = Array.prototype.map.call(arguments, function (s) {
if (typeof s === 'string') {
return s
} else {
@ -89,10 +112,10 @@ module.exports = function (Y) {
var self = this
return new Promise(function (resolve) {
self.requestTransaction(function * () {
var ungc = self.gc1.concat(self.gc2)
var ungc /* :Array<Struct> */ = self.gc1.concat(self.gc2)
self.gc1 = []
self.gc2 = []
for (var i in ungc) {
for (var i = 0; i < ungc.length; i++) {
var op = yield* this.getOperation(ungc[i])
delete op.gc
yield* this.setOperation(op)
@ -145,7 +168,8 @@ module.exports = function (Y) {
return new Promise(function (resolve) {
self.requestTransaction(function * () {
self.userId = userId
self.opClock = (yield* this.getState(userId)).clock
var state = yield* this.getState(userId)
self.opClock = state.clock
if (self.whenUserIdSetListener != null) {
self.whenUserIdSetListener()
self.whenUserIdSetListener = null
@ -225,7 +249,7 @@ module.exports = function (Y) {
store.listenersByIdRequestPending = false
for (let key in exeNow) {
for (let key = 0; key < exeNow.length; key++) {
let o = exeNow[key].op
yield* store.tryExecute.call(this, o)
}
@ -233,7 +257,8 @@ module.exports = function (Y) {
for (var sid in ls) {
var l = ls[sid]
var id = JSON.parse(sid)
if ((yield* this.getOperation(id)) == null) {
var op = yield* this.getOperation(id)
if (op == null) {
store.listenersById[sid] = l
} else {
for (let key in l) {
@ -250,15 +275,28 @@ module.exports = function (Y) {
/*
Actually execute an operation, when all expected operations are available.
*/
/* :: // TODO: this belongs somehow to transaction
store: Object;
getOperation: any;
isGarbageCollected: any;
addOperation: any;
whenOperationsExist: any;
*/
* tryExecute (op) {
this.store.addToDebug('yield* this.store.tryExecute.call(this, ', JSON.stringify(op), ')')
if (op.struct === 'Delete') {
yield* Y.Struct.Delete.execute.call(this, op)
yield* this.store.operationAdded(this, op)
} else if ((yield* this.getOperation(op.id)) == null && !(yield* this.isGarbageCollected(op.id))) {
yield* Y.Struct[op.struct].execute.call(this, op)
yield* this.addOperation(op)
yield* this.store.operationAdded(this, op)
} else {
var defined = yield* this.getOperation(op.id)
if (defined == null) {
var isGarbageCollected = yield* this.isGarbageCollected(op.id)
if (!isGarbageCollected) {
yield* Y.Struct[op.struct].execute.call(this, op)
yield* this.addOperation(op)
yield* this.store.operationAdded(this, op)
}
}
}
}
// called by a transaction when an operation is added
@ -300,30 +338,38 @@ module.exports = function (Y) {
}
}
var t = this.initializedTypes[JSON.stringify(op.parent)]
// notify parent, if it has been initialized as a custom type
if (t != null) {
yield* t._changed(transaction, Y.utils.copyObject(op))
}
// Delete if DS says this is actually deleted
if (!op.deleted && (yield* transaction.isDeleted(op.id))) {
var opIsDeleted = yield* transaction.isDeleted(op.id)
if (!op.deleted && opIsDeleted) {
var delop = {
struct: 'Delete',
target: op.id
}
yield* Y.Struct['Delete'].execute.call(transaction, delop)
if (t != null) {
yield* t._changed(transaction, delop)
}
}
// notify parent, if it has been initialized as a custom type
if (t != null) {
yield* t._changed(transaction, Y.utils.copyObject(op))
}
}
}
whenTransactionsFinished () {
if (this.transactionInProgress) {
if (this.transactionsFinished == null) {
this.transactionsFinished = Promise.defer()
var resolve
var promise = new Promise(function (r) {
resolve = r
})
this.transactionsFinished = {
resolve: resolve,
promise: promise
}
return promise
} else {
return this.transactionsFinished.promise
}
return this.transactionsFinished.promise
} else {
return Promise.resolve()
}
@ -340,8 +386,8 @@ module.exports = function (Y) {
return this.waitingTransactions.shift()
}
}
requestTransaction (makeGen, callImmediately) {
if (callImmediately || true) {
requestTransaction (makeGen/* :any */, callImmediately) {
if (callImmediately) {
this.waitingTransactions.push(makeGen)
if (!this.transactionInProgress) {
this.transactionInProgress = true

View File

@ -37,12 +37,12 @@ g.describeManyTimes = function describeManyTimes (times, name, f) {
*/
function wait (t) {
if (t == null) {
t = 5
t = 0
}
return new Promise(function (resolve) {
setTimeout(function () {
resolve()
}, t * 3)
}, t)
})
}
g.wait = wait
@ -162,7 +162,8 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
var buffer = Y.utils.globalRoom.buffers
for (var name in buffer) {
if (buffer[name].length > 0) {
debugger // not all ops were transmitted..
// not all ops were transmitted..
debugger // eslint-disable-line
}
}

View File

@ -157,7 +157,6 @@ module.exports = function (Y) {
insert.id = this.os.getNextOpId()
var eventHandler = this.eventHandler
eventHandler.awaitAndPrematurelyCall([insert])
this.os.requestTransaction(function *() {
yield* this.applyCreatedOperations([insert])
eventHandler.awaitedInserts(1)
@ -258,7 +257,8 @@ module.exports = function (Y) {
}
* _changed (transaction, op) {
if (op.struct === 'Delete') {
op.key = (yield* transaction.getOperation(op.target)).parentSub
var target = yield* transaction.getOperation(op.target)
op.key = target.parentSub
}
this.eventHandler.receivedOp(op)
}

View File

@ -1,3 +1,4 @@
/* @flow */
'use strict'
/*
@ -6,24 +7,32 @@
Why: When constructing custom types, you sometimes want your types to work
synchronous: E.g.
``` Synchronous
mytype.setSomething("yay")
mytype.getSomething() === "yay"
```
``` Asynchronous
mytype.setSomething("yay")
mytype.getSomething() === undefined
mytype.waitForSomething().then(function(){
mytype.setSomething("yay")
mytype.getSomething() === "yay"
})
```
versus
``` Asynchronous
mytype.setSomething("yay")
mytype.getSomething() === undefined
mytype.waitForSomething().then(function(){
mytype.getSomething() === "yay"
})
```
The structures usually work asynchronously (you have to wait for the
database request to finish). EventHandler will help you to make your type
synchronously.
synchronous.
*/
module.exports = function (Y) {
module.exports = function (Y /* : YGlobal*/) {
Y.utils = {}
class EventHandler {
/* ::
waiting: Array<Insertion | Deletion>;
awaiting: number;
onevent: Function;
eventListeners: Array<Function>;
*/
/*
onevent: is called when the structure changes.
@ -31,7 +40,7 @@ module.exports = function (Y) {
prematurely called. Events for received operations can not be executed until
all prematurely called operations were executed ("waiting operations")
*/
constructor (onevent) {
constructor (onevent /* : Function */) {
this.waiting = []
this.awaiting = 0
this.onevent = onevent
@ -73,7 +82,7 @@ module.exports = function (Y) {
this.eventListeners = []
}
callEventListeners (event) {
for (var i in this.eventListeners) {
for (var i = 0; i < this.eventListeners.length; i++) {
try {
this.eventListeners[i](event)
} catch (e) {
@ -88,18 +97,24 @@ module.exports = function (Y) {
var ops = this.waiting.splice(this.waiting.length - n)
for (var oid = 0; oid < ops.length; oid++) {
var op = ops[oid]
for (var i = this.waiting.length - 1; i >= 0; i--) {
let w = this.waiting[i]
if (Y.utils.compareIds(op.left, w.id)) {
// include the effect of op in w
w.right = op.id
// exclude the effect of w in op
op.left = w.left
} else if (Y.utils.compareIds(op.right, w.id)) {
// similar..
w.left = op.id
op.right = w.right
if (op.struct === 'Insert') {
for (var i = this.waiting.length - 1; i >= 0; i--) {
let w = this.waiting[i]
if (w.struct === 'Insert') {
if (Y.utils.compareIds(op.left, w.id)) {
// include the effect of op in w
w.right = op.id
// exclude the effect of w in op
op.left = w.left
} else if (Y.utils.compareIds(op.right, w.id)) {
// similar..
w.left = op.id
op.right = w.right
}
}
}
} else {
throw new Error('Expected Insert Operation!')
}
}
this._tryCallEvents()
@ -109,16 +124,20 @@ module.exports = function (Y) {
*/
awaitedDeletes (n, newLeft) {
var ops = this.waiting.splice(this.waiting.length - n)
for (var j in ops) {
for (var j = 0; j < ops.length; j++) {
var del = ops[j]
if (newLeft != null) {
for (var i in this.waiting) {
let w = this.waiting[i]
// We will just care about w.left
if (Y.utils.compareIds(del.target, w.left)) {
del.left = newLeft
if (del.struct === 'Delete') {
if (newLeft != null) {
for (var i = 0; i < this.waiting.length; i++) {
let w = this.waiting[i]
// We will just care about w.left
if (w.struct === 'Insert' && Y.utils.compareIds(del.target, w.left)) {
w.left = newLeft
}
}
}
} else {
throw new Error('Expected Delete Operation!')
}
}
this._tryCallEvents()
@ -149,6 +168,11 @@ module.exports = function (Y) {
- the constructor of the custom type (e.g. in order to inherit from a type)
*/
class CustomType { // eslint-disable-line
/* ::
createType: any;
initType: any;
class: Function;
*/
constructor (def) {
if (def.createType == null ||
def.initType == null ||

View File

@ -1,4 +1,3 @@
/* @flow */
'use strict'
require('./Connector.js')(Y)
@ -20,7 +19,8 @@ Y.extend = function (name, value) {
}
}
Y.requestModules = function (modules) {
Y.requestModules = requestModules
function requestModules (modules) {
var promises = []
for (var i = 0; i < modules.length; i++) {
var modulename = 'y-' + modules[i].toLowerCase()