* 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. // 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": [ "ignore": [
"build/**", "build/**",
"dist/**", "dist/**",
"declarations/**",
"./y.js", "./y.js",
"./y.js.map" "./y.js.map"
] ]

View File

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

View File

@ -1,6 +1,7 @@
/* @flow */
'use strict' 'use strict'
module.exports = function (Y) { module.exports = function (Y /* : YGlobal */) {
/* /*
Partial definition of an OperationStore. Partial definition of an OperationStore.
TODO: name it Database, operation store only holds operations. TODO: name it Database, operation store only holds operations.
@ -14,6 +15,28 @@ module.exports = function (Y) {
- destroy the database - destroy the database
*/ */
class AbstractDatabase { 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) { constructor (y, opts) {
this.y = y this.y = y
// whether to broadcast all applied operations (insert & delete hook) // whether to broadcast all applied operations (insert & delete hook)
@ -51,7 +74,7 @@ module.exports = function (Y) {
return new Promise((resolve) => { return new Promise((resolve) => {
os.requestTransaction(function * () { os.requestTransaction(function * () {
if (os.y.connector != null && os.y.connector.isSynced) { 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] var oid = os.gc2[i]
yield* this.garbageCollectOperation(oid) yield* this.garbageCollectOperation(oid)
} }
@ -72,7 +95,7 @@ module.exports = function (Y) {
} }
addToDebug () { addToDebug () {
if (typeof YConcurrency_TestingMode !== 'undefined') { 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') { if (typeof s === 'string') {
return s return s
} else { } else {
@ -89,10 +112,10 @@ module.exports = function (Y) {
var self = this var self = this
return new Promise(function (resolve) { return new Promise(function (resolve) {
self.requestTransaction(function * () { self.requestTransaction(function * () {
var ungc = self.gc1.concat(self.gc2) var ungc /* :Array<Struct> */ = self.gc1.concat(self.gc2)
self.gc1 = [] self.gc1 = []
self.gc2 = [] self.gc2 = []
for (var i in ungc) { for (var i = 0; i < ungc.length; i++) {
var op = yield* this.getOperation(ungc[i]) var op = yield* this.getOperation(ungc[i])
delete op.gc delete op.gc
yield* this.setOperation(op) yield* this.setOperation(op)
@ -145,7 +168,8 @@ module.exports = function (Y) {
return new Promise(function (resolve) { return new Promise(function (resolve) {
self.requestTransaction(function * () { self.requestTransaction(function * () {
self.userId = userId self.userId = userId
self.opClock = (yield* this.getState(userId)).clock var state = yield* this.getState(userId)
self.opClock = state.clock
if (self.whenUserIdSetListener != null) { if (self.whenUserIdSetListener != null) {
self.whenUserIdSetListener() self.whenUserIdSetListener()
self.whenUserIdSetListener = null self.whenUserIdSetListener = null
@ -225,7 +249,7 @@ module.exports = function (Y) {
store.listenersByIdRequestPending = false store.listenersByIdRequestPending = false
for (let key in exeNow) { for (let key = 0; key < exeNow.length; key++) {
let o = exeNow[key].op let o = exeNow[key].op
yield* store.tryExecute.call(this, o) yield* store.tryExecute.call(this, o)
} }
@ -233,7 +257,8 @@ module.exports = function (Y) {
for (var sid in ls) { for (var sid in ls) {
var l = ls[sid] var l = ls[sid]
var id = JSON.parse(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 store.listenersById[sid] = l
} else { } else {
for (let key in l) { for (let key in l) {
@ -250,15 +275,28 @@ module.exports = function (Y) {
/* /*
Actually execute an operation, when all expected operations are available. 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) { * tryExecute (op) {
this.store.addToDebug('yield* this.store.tryExecute.call(this, ', JSON.stringify(op), ')') this.store.addToDebug('yield* this.store.tryExecute.call(this, ', JSON.stringify(op), ')')
if (op.struct === 'Delete') { if (op.struct === 'Delete') {
yield* Y.Struct.Delete.execute.call(this, op) yield* Y.Struct.Delete.execute.call(this, op)
yield* this.store.operationAdded(this, op) yield* this.store.operationAdded(this, op)
} else if ((yield* this.getOperation(op.id)) == null && !(yield* this.isGarbageCollected(op.id))) { } else {
yield* Y.Struct[op.struct].execute.call(this, op) var defined = yield* this.getOperation(op.id)
yield* this.addOperation(op) if (defined == null) {
yield* this.store.operationAdded(this, op) 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 // 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)] 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 // 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 = { var delop = {
struct: 'Delete', struct: 'Delete',
target: op.id target: op.id
} }
yield* Y.Struct['Delete'].execute.call(transaction, delop) 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 () { whenTransactionsFinished () {
if (this.transactionInProgress) { if (this.transactionInProgress) {
if (this.transactionsFinished == null) { 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 { } else {
return Promise.resolve() return Promise.resolve()
} }
@ -340,8 +386,8 @@ module.exports = function (Y) {
return this.waitingTransactions.shift() return this.waitingTransactions.shift()
} }
} }
requestTransaction (makeGen, callImmediately) { requestTransaction (makeGen/* :any */, callImmediately) {
if (callImmediately || true) { if (callImmediately) {
this.waitingTransactions.push(makeGen) this.waitingTransactions.push(makeGen)
if (!this.transactionInProgress) { if (!this.transactionInProgress) {
this.transactionInProgress = true this.transactionInProgress = true

View File

@ -37,12 +37,12 @@ g.describeManyTimes = function describeManyTimes (times, name, f) {
*/ */
function wait (t) { function wait (t) {
if (t == null) { if (t == null) {
t = 5 t = 0
} }
return new Promise(function (resolve) { return new Promise(function (resolve) {
setTimeout(function () { setTimeout(function () {
resolve() resolve()
}, t * 3) }, t)
}) })
} }
g.wait = wait g.wait = wait
@ -162,7 +162,8 @@ g.compareAllUsers = async(function * compareAllUsers (users) {
var buffer = Y.utils.globalRoom.buffers var buffer = Y.utils.globalRoom.buffers
for (var name in buffer) { for (var name in buffer) {
if (buffer[name].length > 0) { 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() insert.id = this.os.getNextOpId()
var eventHandler = this.eventHandler var eventHandler = this.eventHandler
eventHandler.awaitAndPrematurelyCall([insert]) eventHandler.awaitAndPrematurelyCall([insert])
this.os.requestTransaction(function *() { this.os.requestTransaction(function *() {
yield* this.applyCreatedOperations([insert]) yield* this.applyCreatedOperations([insert])
eventHandler.awaitedInserts(1) eventHandler.awaitedInserts(1)
@ -258,7 +257,8 @@ module.exports = function (Y) {
} }
* _changed (transaction, op) { * _changed (transaction, op) {
if (op.struct === 'Delete') { 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) this.eventHandler.receivedOp(op)
} }

View File

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

View File

@ -1,4 +1,3 @@
/* @flow */
'use strict' 'use strict'
require('./Connector.js')(Y) 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 = [] var promises = []
for (var i = 0; i < modules.length; i++) { for (var i = 0; i < modules.length; i++) {
var modulename = 'y-' + modules[i].toLowerCase() var modulename = 'y-' + modules[i].toLowerCase()