382 lines
11 KiB
JavaScript
382 lines
11 KiB
JavaScript
/* @flow */
|
|
'use strict'
|
|
|
|
/*
|
|
EventHandler is an helper class for constructing custom types.
|
|
|
|
Why: When constructing custom types, you sometimes want your types to work
|
|
synchronous: E.g.
|
|
``` Synchronous
|
|
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
|
|
synchronous.
|
|
*/
|
|
module.exports = function (Y /* : any*/) {
|
|
Y.utils = {}
|
|
|
|
class EventHandler {
|
|
/* ::
|
|
waiting: Array<Insertion | Deletion>;
|
|
awaiting: number;
|
|
onevent: Function;
|
|
eventListeners: Array<Function>;
|
|
*/
|
|
/*
|
|
onevent: is called when the structure changes.
|
|
|
|
Note: "awaiting opertations" is used to denote operations that were
|
|
prematurely called. Events for received operations can not be executed until
|
|
all prematurely called operations were executed ("waiting operations")
|
|
*/
|
|
constructor (onevent /* : Function */) {
|
|
this.waiting = []
|
|
this.awaiting = 0
|
|
this.onevent = onevent
|
|
this.eventListeners = []
|
|
}
|
|
/*
|
|
Call this when a new operation arrives. It will be executed right away if
|
|
there are no waiting operations, that you prematurely executed
|
|
*/
|
|
receivedOp (op) {
|
|
if (this.awaiting <= 0) {
|
|
this.onevent([op])
|
|
} else {
|
|
this.waiting.push(Y.utils.copyObject(op))
|
|
}
|
|
}
|
|
/*
|
|
You created some operations, and you want the `onevent` function to be
|
|
called right away. Received operations will not be executed untill all
|
|
prematurely called operations are executed
|
|
*/
|
|
awaitAndPrematurelyCall (ops) {
|
|
this.awaiting++
|
|
this.onevent(ops)
|
|
}
|
|
/*
|
|
Basic event listener boilerplate...
|
|
TODO: maybe put this in a different type..
|
|
*/
|
|
addEventListener (f) {
|
|
this.eventListeners.push(f)
|
|
}
|
|
removeEventListener (f) {
|
|
this.eventListeners = this.eventListeners.filter(function (g) {
|
|
return f !== g
|
|
})
|
|
}
|
|
removeAllEventListeners () {
|
|
this.eventListeners = []
|
|
}
|
|
callEventListeners (event) {
|
|
for (var i = 0; i < this.eventListeners.length; i++) {
|
|
try {
|
|
this.eventListeners[i](event)
|
|
} catch (e) {
|
|
console.log('User events must not throw Errors!') // eslint-disable-line
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
Call this when you successfully awaited the execution of n Insert operations
|
|
*/
|
|
awaitedInserts (n) {
|
|
var ops = this.waiting.splice(this.waiting.length - n)
|
|
for (var oid = 0; oid < ops.length; oid++) {
|
|
var op = ops[oid]
|
|
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()
|
|
}
|
|
/*
|
|
Call this when you successfully awaited the execution of n Delete operations
|
|
*/
|
|
awaitedDeletes (n, newLeft) {
|
|
var ops = this.waiting.splice(this.waiting.length - n)
|
|
for (var j = 0; j < ops.length; j++) {
|
|
var del = ops[j]
|
|
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()
|
|
}
|
|
/* (private)
|
|
Try to execute the events for the waiting operations
|
|
*/
|
|
_tryCallEvents () {
|
|
this.awaiting--
|
|
if (this.awaiting <= 0 && this.waiting.length > 0) {
|
|
var events = this.waiting
|
|
this.waiting = []
|
|
this.onevent(events)
|
|
}
|
|
}
|
|
}
|
|
Y.utils.EventHandler = EventHandler
|
|
|
|
/*
|
|
A wrapper for the definition of a custom type.
|
|
Every custom type must have three properties:
|
|
|
|
* struct
|
|
- Structname of this type
|
|
* initType
|
|
- Given a model, creates a custom type
|
|
* class
|
|
- the constructor of the custom type (e.g. in order to inherit from a type)
|
|
*/
|
|
class CustomType { // eslint-disable-line
|
|
/* ::
|
|
struct: any;
|
|
initType: any;
|
|
class: Function;
|
|
name: String;
|
|
*/
|
|
constructor (def) {
|
|
if (def.struct == null ||
|
|
def.initType == null ||
|
|
def.class == null ||
|
|
def.name == null
|
|
) {
|
|
throw new Error('Custom type was not initialized correctly!')
|
|
}
|
|
this.struct = def.struct
|
|
this.initType = def.initType
|
|
this.class = def.class
|
|
this.name = def.name
|
|
}
|
|
}
|
|
Y.utils.CustomType = CustomType
|
|
|
|
/*
|
|
Make a flat copy of an object
|
|
(just copy properties)
|
|
*/
|
|
function copyObject (o) {
|
|
var c = {}
|
|
for (var key in o) {
|
|
c[key] = o[key]
|
|
}
|
|
return c
|
|
}
|
|
Y.utils.copyObject = copyObject
|
|
|
|
/*
|
|
Defines a smaller relation on Id's
|
|
*/
|
|
function smaller (a, b) {
|
|
return a[0] < b[0] || (a[0] === b[0] && (a[1] < b[1] || typeof a[1] < typeof b[1]))
|
|
}
|
|
Y.utils.smaller = smaller
|
|
|
|
function compareIds (id1, id2) {
|
|
if (id1 == null || id2 == null) {
|
|
if (id1 == null && id2 == null) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
if (id1[0] === id2[0] && id1[1] === id2[1]) {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}
|
|
Y.utils.compareIds = compareIds
|
|
|
|
function createEmptyOpsArray (n) {
|
|
var a = new Array(n)
|
|
for (var i = 0; i < a.length; i++) {
|
|
a[i] = {
|
|
id: [null, null]
|
|
}
|
|
}
|
|
return a
|
|
}
|
|
|
|
function createSmallLookupBuffer (Store) {
|
|
/*
|
|
This buffer implements a very small buffer that temporarily stores operations
|
|
after they are read / before they are written.
|
|
The buffer basically implements FIFO. Often requested lookups will be re-queued every time they are looked up / written.
|
|
|
|
It can speed up lookups on Operation Stores and State Stores. But it does not require notable use of memory or processing power.
|
|
|
|
Good for os and ss, bot not for ds (because it often uses methods that require a flush)
|
|
|
|
I tried to optimize this for performance, therefore no highlevel operations.
|
|
*/
|
|
class SmallLookupBuffer extends Store {
|
|
constructor () {
|
|
super(...arguments)
|
|
this.writeBuffer = createEmptyOpsArray(5)
|
|
this.readBuffer = createEmptyOpsArray(10)
|
|
}
|
|
* find (id) {
|
|
var i, r
|
|
for (i = this.readBuffer.length - 1; i >= 0; i--) {
|
|
r = this.readBuffer[i]
|
|
// we don't have to use compareids, because id is always defined!
|
|
if (r.id[1] === id[1] && r.id[0] === id[0]) {
|
|
// found r
|
|
// move r to the end of readBuffer
|
|
for (; i < this.readBuffer.length - 1; i++) {
|
|
this.readBuffer[i] = this.readBuffer[i + 1]
|
|
}
|
|
this.readBuffer[this.readBuffer.length - 1] = r
|
|
return r
|
|
}
|
|
}
|
|
var o
|
|
for (i = this.writeBuffer.length - 1; i >= 0; i--) {
|
|
r = this.writeBuffer[i]
|
|
if (r.id[1] === id[1] && r.id[0] === id[0]) {
|
|
o = r
|
|
break
|
|
}
|
|
}
|
|
if (i < 0) {
|
|
// did not reach break in last loop
|
|
// read id and put it to the end of readBuffer
|
|
o = yield* super.find(id)
|
|
}
|
|
if (o != null) {
|
|
for (i = 0; i < this.readBuffer.length - 1; i++) {
|
|
this.readBuffer[i] = this.readBuffer[i + 1]
|
|
}
|
|
this.readBuffer[this.readBuffer.length - 1] = o
|
|
}
|
|
return o
|
|
}
|
|
* put (o) {
|
|
var id = o.id
|
|
var i, r // helper variables
|
|
for (i = this.writeBuffer.length - 1; i >= 0; i--) {
|
|
r = this.writeBuffer[i]
|
|
if (r.id[1] === id[1] && r.id[0] === id[0]) {
|
|
// is already in buffer
|
|
// forget r, and move o to the end of writeBuffer
|
|
for (; i < this.writeBuffer.length - 1; i++) {
|
|
this.writeBuffer[i] = this.writeBuffer[i + 1]
|
|
}
|
|
this.writeBuffer[this.writeBuffer.length - 1] = o
|
|
break
|
|
}
|
|
}
|
|
if (i < 0) {
|
|
// did not reach break in last loop
|
|
// write writeBuffer[0]
|
|
var write = this.writeBuffer[0]
|
|
if (write.id[0] !== null) {
|
|
yield* super.put(write)
|
|
}
|
|
// put o to the end of writeBuffer
|
|
for (i = 0; i < this.writeBuffer.length - 1; i++) {
|
|
this.writeBuffer[i] = this.writeBuffer[i + 1]
|
|
}
|
|
this.writeBuffer[this.writeBuffer.length - 1] = o
|
|
}
|
|
// check readBuffer for every occurence of o.id, overwrite if found
|
|
// whether found or not, we'll append o to the readbuffer
|
|
for (i = 0; i < this.readBuffer.length - 1; i++) {
|
|
r = this.readBuffer[i + 1]
|
|
if (r.id[1] === id[1] && r.id[0] === id[0]) {
|
|
this.readBuffer[i] = o
|
|
} else {
|
|
this.readBuffer[i] = r
|
|
}
|
|
}
|
|
this.readBuffer[this.readBuffer.length - 1] = o
|
|
}
|
|
* delete (id) {
|
|
var i, r
|
|
for (i = 0; i < this.readBuffer.length; i++) {
|
|
r = this.readBuffer[i]
|
|
if (r.id[1] === id[1] && r.id[0] === id[0]) {
|
|
this.readBuffer[i] = {
|
|
id: [null, null]
|
|
}
|
|
}
|
|
}
|
|
yield* this.flush()
|
|
yield* super.delete(id)
|
|
}
|
|
* findWithLowerBound () {
|
|
yield* this.flush()
|
|
return yield* super.findWithLowerBound.apply(this, arguments)
|
|
}
|
|
* findWithUpperBound () {
|
|
yield* this.flush()
|
|
return yield* super.findWithUpperBound.apply(this, arguments)
|
|
}
|
|
* findNext () {
|
|
yield* this.flush()
|
|
return yield* super.findNext.apply(this, arguments)
|
|
}
|
|
* findPrev () {
|
|
yield* this.flush()
|
|
return yield* super.findPrev.apply(this, arguments)
|
|
}
|
|
* iterate () {
|
|
yield* this.flush()
|
|
yield* super.iterate.apply(this, arguments)
|
|
}
|
|
* flush () {
|
|
for (var i = 0; i < this.writeBuffer.length; i++) {
|
|
var write = this.writeBuffer[i]
|
|
if (write.id[0] !== null) {
|
|
yield* super.put(write)
|
|
this.writeBuffer[i] = {
|
|
id: [null, null]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return SmallLookupBuffer
|
|
}
|
|
Y.utils.createSmallLookupBuffer = createSmallLookupBuffer
|
|
}
|