857 lines
27 KiB
JavaScript
857 lines
27 KiB
JavaScript
/* globals crypto */
|
|
|
|
import { BinaryDecoder, BinaryEncoder } from './Encoding.js'
|
|
|
|
/*
|
|
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 helps you to make your type
|
|
synchronous.
|
|
*/
|
|
|
|
export default function Utils (Y) {
|
|
Y.utils = {
|
|
BinaryDecoder: BinaryDecoder,
|
|
BinaryEncoder: BinaryEncoder
|
|
}
|
|
|
|
Y.utils.bubbleEvent = function (type, event) {
|
|
type.eventHandler.callEventListeners(event)
|
|
event.path = []
|
|
while (type != null && type._deepEventHandler != null) {
|
|
type._deepEventHandler.callEventListeners(event)
|
|
var parent = null
|
|
if (type._parent != null) {
|
|
parent = type.os.getType(type._parent)
|
|
}
|
|
if (parent != null && parent._getPathToChild != null) {
|
|
event.path = [parent._getPathToChild(type._model)].concat(event.path)
|
|
type = parent
|
|
} else {
|
|
type = null
|
|
}
|
|
}
|
|
}
|
|
|
|
class NamedEventHandler {
|
|
constructor () {
|
|
this._eventListener = {}
|
|
}
|
|
on (name, f) {
|
|
if (this._eventListener[name] == null) {
|
|
this._eventListener[name] = []
|
|
}
|
|
this._eventListener[name].push(f)
|
|
}
|
|
off (name, f) {
|
|
if (name == null || f == null) {
|
|
throw new Error('You must specify event name and function!')
|
|
}
|
|
let listener = this._eventListener[name] || []
|
|
this._eventListener[name] = listener.filter(e => e !== f)
|
|
}
|
|
emit (name, value) {
|
|
(this._eventListener[name] || []).forEach(l => l(value))
|
|
}
|
|
destroy () {
|
|
this._eventListener = null
|
|
}
|
|
}
|
|
Y.utils.NamedEventHandler = NamedEventHandler
|
|
|
|
class EventListenerHandler {
|
|
constructor () {
|
|
this.eventListeners = []
|
|
}
|
|
destroy () {
|
|
this.eventListeners = null
|
|
}
|
|
/*
|
|
Basic event listener boilerplate...
|
|
*/
|
|
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 {
|
|
var _event = {}
|
|
for (var name in event) {
|
|
_event[name] = event[name]
|
|
}
|
|
this.eventListeners[i](_event)
|
|
} catch (e) {
|
|
/*
|
|
Your observer threw an error. This error was caught so that Yjs
|
|
can ensure data consistency! In order to debug this error you
|
|
have to check "Pause On Caught Exceptions" in developer tools.
|
|
*/
|
|
console.error(e)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Y.utils.EventListenerHandler = EventListenerHandler
|
|
|
|
class EventHandler extends EventListenerHandler {
|
|
/* ::
|
|
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 */) {
|
|
super()
|
|
this.waiting = []
|
|
this.awaiting = 0
|
|
this.onevent = onevent
|
|
}
|
|
destroy () {
|
|
super.destroy()
|
|
this.waiting = null
|
|
this.onevent = null
|
|
}
|
|
/*
|
|
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 if (op.struct === 'Delete') {
|
|
var self = this
|
|
var checkDelete = function checkDelete (d) {
|
|
if (d.length == null) {
|
|
throw new Error('This shouldn\'t happen! d.length must be defined!')
|
|
}
|
|
// we check if o deletes something in self.waiting
|
|
// if so, we remove the deleted operation
|
|
for (var w = 0; w < self.waiting.length; w++) {
|
|
var i = self.waiting[w]
|
|
if (i.struct === 'Insert' && i.id[0] === d.target[0]) {
|
|
var iLength = i.hasOwnProperty('content') ? i.content.length : 1
|
|
var dStart = d.target[1]
|
|
var dEnd = d.target[1] + (d.length || 1)
|
|
var iStart = i.id[1]
|
|
var iEnd = i.id[1] + iLength
|
|
// Check if they don't overlap
|
|
if (iEnd <= dStart || dEnd <= iStart) {
|
|
// no overlapping
|
|
continue
|
|
}
|
|
// we check all overlapping cases. All cases:
|
|
/*
|
|
1) iiiii
|
|
ddddd
|
|
--> modify i and d
|
|
2) iiiiiii
|
|
ddddd
|
|
--> modify i, remove d
|
|
3) iiiiiii
|
|
ddd
|
|
--> remove d, modify i, and create another i (for the right hand side)
|
|
4) iiiii
|
|
ddddddd
|
|
--> remove i, modify d
|
|
5) iiiiiii
|
|
ddddddd
|
|
--> remove both i and d (**)
|
|
6) iiiiiii
|
|
ddddd
|
|
--> modify i, remove d
|
|
7) iii
|
|
ddddddd
|
|
--> remove i, create and apply two d with checkDelete(d) (**)
|
|
8) iiiii
|
|
ddddddd
|
|
--> remove i, modify d (**)
|
|
9) iiiii
|
|
ddddd
|
|
--> modify i and d
|
|
(**) (also check if i contains content or type)
|
|
*/
|
|
// TODO: I left some debugger statements, because I want to debug all cases once in production. REMEMBER END TODO
|
|
if (iStart < dStart) {
|
|
if (dStart < iEnd) {
|
|
if (iEnd < dEnd) {
|
|
// Case 1
|
|
// remove the right part of i's content
|
|
i.content.splice(dStart - iStart)
|
|
// remove the start of d's deletion
|
|
d.length = dEnd - iEnd
|
|
d.target = [d.target[0], iEnd]
|
|
continue
|
|
} else if (iEnd === dEnd) {
|
|
// Case 2
|
|
i.content.splice(dStart - iStart)
|
|
// remove d, we do that by simply ending this function
|
|
return
|
|
} else { // (dEnd < iEnd)
|
|
// Case 3
|
|
var newI = {
|
|
id: [i.id[0], dEnd],
|
|
content: i.content.slice(dEnd - iStart),
|
|
struct: 'Insert'
|
|
}
|
|
self.waiting.push(newI)
|
|
i.content.splice(dStart - iStart)
|
|
return
|
|
}
|
|
}
|
|
} else if (dStart === iStart) {
|
|
if (iEnd < dEnd) {
|
|
// Case 4
|
|
d.length = dEnd - iEnd
|
|
d.target = [d.target[0], iEnd]
|
|
i.content = []
|
|
continue
|
|
} else if (iEnd === dEnd) {
|
|
// Case 5
|
|
self.waiting.splice(w, 1)
|
|
return
|
|
} else { // (dEnd < iEnd)
|
|
// Case 6
|
|
i.content = i.content.slice(dEnd - iStart)
|
|
i.id = [i.id[0], dEnd]
|
|
return
|
|
}
|
|
} else { // (dStart < iStart)
|
|
if (iStart < dEnd) {
|
|
// they overlap
|
|
/*
|
|
7) iii
|
|
ddddddd
|
|
--> remove i, create and apply two d with checkDelete(d) (**)
|
|
8) iiiii
|
|
ddddddd
|
|
--> remove i, modify d (**)
|
|
9) iiiii
|
|
ddddd
|
|
--> modify i and d
|
|
*/
|
|
if (iEnd < dEnd) {
|
|
// Case 7
|
|
// debugger // TODO: You did not test this case yet!!!! (add the debugger here)
|
|
self.waiting.splice(w, 1)
|
|
checkDelete({
|
|
target: [d.target[0], dStart],
|
|
length: iStart - dStart,
|
|
struct: 'Delete'
|
|
})
|
|
checkDelete({
|
|
target: [d.target[0], iEnd],
|
|
length: iEnd - dEnd,
|
|
struct: 'Delete'
|
|
})
|
|
return
|
|
} else if (iEnd === dEnd) {
|
|
// Case 8
|
|
self.waiting.splice(w, 1)
|
|
w--
|
|
d.length -= iLength
|
|
continue
|
|
} else { // dEnd < iEnd
|
|
// Case 9
|
|
d.length = iStart - dStart
|
|
i.content.splice(0, dEnd - iStart)
|
|
i.id = [i.id[0], dEnd]
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// finished with remaining operations
|
|
self.waiting.push(d)
|
|
}
|
|
if (op.key == null) {
|
|
// deletes in list
|
|
checkDelete(op)
|
|
} else {
|
|
// deletes in map
|
|
this.waiting.push(op)
|
|
}
|
|
} else {
|
|
this.waiting.push(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++
|
|
ops.map(Y.utils.copyOperation).forEach(this.onevent)
|
|
}
|
|
* awaitOps (transaction, f, args) {
|
|
function notSoSmartSort (array) {
|
|
// this function sorts insertions in a executable order
|
|
var result = []
|
|
while (array.length > 0) {
|
|
for (var i = 0; i < array.length; i++) {
|
|
var independent = true
|
|
for (var j = 0; j < array.length; j++) {
|
|
if (Y.utils.matchesId(array[j], array[i].left)) {
|
|
// array[i] depends on array[j]
|
|
independent = false
|
|
break
|
|
}
|
|
}
|
|
if (independent) {
|
|
result.push(array.splice(i, 1)[0])
|
|
i--
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
var before = this.waiting.length
|
|
// somehow create new operations
|
|
yield * f.apply(transaction, args)
|
|
// remove all appended ops / awaited ops
|
|
this.waiting.splice(before)
|
|
if (this.awaiting > 0) this.awaiting--
|
|
// if there are no awaited ops anymore, we can update all waiting ops, and send execute them (if there are still no awaited ops)
|
|
if (this.awaiting === 0 && this.waiting.length > 0) {
|
|
// update all waiting ops
|
|
for (let i = 0; i < this.waiting.length; i++) {
|
|
var o = this.waiting[i]
|
|
if (o.struct === 'Insert') {
|
|
var _o = yield * transaction.getInsertion(o.id)
|
|
if (_o.parentSub != null && _o.left != null) {
|
|
// if o is an insertion of a map struc (parentSub is defined), then it shouldn't be necessary to compute left
|
|
this.waiting.splice(i, 1)
|
|
i-- // update index
|
|
} else if (!Y.utils.compareIds(_o.id, o.id)) {
|
|
// o got extended
|
|
o.left = [o.id[0], o.id[1] - 1]
|
|
} else if (_o.left == null) {
|
|
o.left = null
|
|
} else {
|
|
// find next undeleted op
|
|
var left = yield * transaction.getInsertion(_o.left)
|
|
while (left.deleted != null) {
|
|
if (left.left != null) {
|
|
left = yield * transaction.getInsertion(left.left)
|
|
} else {
|
|
left = null
|
|
break
|
|
}
|
|
}
|
|
o.left = left != null ? Y.utils.getLastId(left) : null
|
|
}
|
|
}
|
|
}
|
|
// the previous stuff was async, so we have to check again!
|
|
// We also pull changes from the bindings, if there exists such a method, this could increase awaiting too
|
|
if (this._pullChanges != null) {
|
|
this._pullChanges()
|
|
}
|
|
if (this.awaiting === 0) {
|
|
// sort by type, execute inserts first
|
|
var ins = []
|
|
var dels = []
|
|
this.waiting.forEach(function (o) {
|
|
if (o.struct === 'Delete') {
|
|
dels.push(o)
|
|
} else {
|
|
ins.push(o)
|
|
}
|
|
})
|
|
this.waiting = []
|
|
// put in executable order
|
|
ins = notSoSmartSort(ins)
|
|
// this.onevent can trigger the creation of another operation
|
|
// -> check if this.awaiting increased & stop computation if it does
|
|
for (var i = 0; i < ins.length; i++) {
|
|
if (this.awaiting === 0) {
|
|
this.onevent(ins[i])
|
|
} else {
|
|
this.waiting = this.waiting.concat(ins.slice(i))
|
|
break
|
|
}
|
|
}
|
|
for (i = 0; i < dels.length; i++) {
|
|
if (this.awaiting === 0) {
|
|
this.onevent(dels[i])
|
|
} else {
|
|
this.waiting = this.waiting.concat(dels.slice(i))
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// TODO: Remove awaitedInserts and awaitedDeletes in favor of awaitedOps, as they are deprecated and do not always work
|
|
// Do this in one of the coming releases that are breaking anyway
|
|
/*
|
|
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]
|
|
// TODO: do I handle split operations correctly here? Super unlikely, but yeah..
|
|
// Also: can this case happen? Can op be inserted in the middle of a larger op that is in $waiting?
|
|
if (w.struct === 'Insert') {
|
|
if (Y.utils.matchesId(w, op.left)) {
|
|
// 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(w.id, op.right)) {
|
|
// similar..
|
|
w.left = Y.utils.getLastId(op)
|
|
op.right = w.right
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
throw new Error('Expected Insert Operation!')
|
|
}
|
|
}
|
|
this._tryCallEvents(n)
|
|
}
|
|
/*
|
|
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(n)
|
|
}
|
|
/* (private)
|
|
Try to execute the events for the waiting operations
|
|
*/
|
|
_tryCallEvents () {
|
|
function notSoSmartSort (array) {
|
|
var result = []
|
|
while (array.length > 0) {
|
|
for (var i = 0; i < array.length; i++) {
|
|
var independent = true
|
|
for (var j = 0; j < array.length; j++) {
|
|
if (Y.utils.matchesId(array[j], array[i].left)) {
|
|
// array[i] depends on array[j]
|
|
independent = false
|
|
break
|
|
}
|
|
}
|
|
if (independent) {
|
|
result.push(array.splice(i, 1)[0])
|
|
i--
|
|
}
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
if (this.awaiting > 0) this.awaiting--
|
|
if (this.awaiting === 0 && this.waiting.length > 0) {
|
|
var ins = []
|
|
var dels = []
|
|
this.waiting.forEach(function (o) {
|
|
if (o.struct === 'Delete') {
|
|
dels.push(o)
|
|
} else {
|
|
ins.push(o)
|
|
}
|
|
})
|
|
ins = notSoSmartSort(ins)
|
|
ins.forEach(this.onevent)
|
|
dels.forEach(this.onevent)
|
|
this.waiting = []
|
|
}
|
|
}
|
|
}
|
|
Y.utils.EventHandler = EventHandler
|
|
|
|
/*
|
|
Default class of custom types!
|
|
*/
|
|
class CustomType {
|
|
getPath () {
|
|
var parent = null
|
|
if (this._parent != null) {
|
|
parent = this.os.getType(this._parent)
|
|
}
|
|
if (parent != null && parent._getPathToChild != null) {
|
|
var firstKey = parent._getPathToChild(this._model)
|
|
var parentKeys = parent.getPath()
|
|
parentKeys.push(firstKey)
|
|
return parentKeys
|
|
} else {
|
|
return []
|
|
}
|
|
}
|
|
}
|
|
Y.utils.CustomType = CustomType
|
|
|
|
/*
|
|
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 CustomTypeDefinition { // 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 ||
|
|
def.createType == null
|
|
) {
|
|
throw new Error('Custom type was not initialized correctly!')
|
|
}
|
|
this.struct = def.struct
|
|
this.initType = def.initType
|
|
this.createType = def.createType
|
|
this.class = def.class
|
|
this.name = def.name
|
|
if (def.appendAdditionalInfo != null) {
|
|
this.appendAdditionalInfo = def.appendAdditionalInfo
|
|
}
|
|
this.parseArguments = (def.parseArguments || function () {
|
|
return [this]
|
|
}).bind(this)
|
|
this.parseArguments.typeDefinition = this
|
|
}
|
|
}
|
|
Y.utils.CustomTypeDefinition = CustomTypeDefinition
|
|
|
|
Y.utils.isTypeDefinition = function isTypeDefinition (v) {
|
|
if (v != null) {
|
|
if (v instanceof Y.utils.CustomTypeDefinition) return [v]
|
|
else if (v.constructor === Array && v[0] instanceof Y.utils.CustomTypeDefinition) return v
|
|
else if (v instanceof Function && v.typeDefinition instanceof Y.utils.CustomTypeDefinition) return [v.typeDefinition]
|
|
}
|
|
return false
|
|
}
|
|
|
|
/*
|
|
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
|
|
|
|
/*
|
|
Copy an operation, so that it can be manipulated.
|
|
Note: You must not change subproperties (except o.content)!
|
|
*/
|
|
function copyOperation (o) {
|
|
o = copyObject(o)
|
|
if (o.content != null) {
|
|
o.content = o.content.map(function (c) { return c })
|
|
}
|
|
return o
|
|
}
|
|
|
|
Y.utils.copyOperation = copyOperation
|
|
|
|
/*
|
|
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 inDeletionRange (del, ins) {
|
|
return del.target[0] === ins[0] && del.target[1] <= ins[1] && ins[1] < del.target[1] + (del.length || 1)
|
|
}
|
|
Y.utils.inDeletionRange = inDeletionRange
|
|
|
|
function compareIds (id1, id2) {
|
|
if (id1 == null || id2 == null) {
|
|
return id1 === id2
|
|
} else {
|
|
return id1[0] === id2[0] && id1[1] === id2[1]
|
|
}
|
|
}
|
|
Y.utils.compareIds = compareIds
|
|
|
|
function matchesId (op, id) {
|
|
if (id == null || op == null) {
|
|
return id === op
|
|
} else {
|
|
if (id[0] === op.id[0]) {
|
|
if (op.content == null) {
|
|
return id[1] === op.id[1]
|
|
} else {
|
|
return id[1] >= op.id[1] && id[1] < op.id[1] + op.content.length
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
Y.utils.matchesId = matchesId
|
|
|
|
function getLastId (op) {
|
|
if (op.content == null || op.content.length === 1) {
|
|
return op.id
|
|
} else {
|
|
return [op.id[0], op.id[1] + op.content.length - 1]
|
|
}
|
|
}
|
|
Y.utils.getLastId = getLastId
|
|
|
|
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 (arg1, arg2) {
|
|
// super(...arguments) -- do this when this is supported by stable nodejs
|
|
super(arg1, arg2)
|
|
this.writeBuffer = createEmptyOpsArray(5)
|
|
this.readBuffer = createEmptyOpsArray(10)
|
|
}
|
|
* find (id, noSuperCall) {
|
|
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 && noSuperCall === undefined) {
|
|
// 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 (id) {
|
|
var o = yield * this.find(id, true)
|
|
if (o != null) {
|
|
return o
|
|
} else {
|
|
yield * this.flush()
|
|
return yield * super.findWithLowerBound.apply(this, arguments)
|
|
}
|
|
}
|
|
* findWithUpperBound (id) {
|
|
var o = yield * this.find(id, true)
|
|
if (o != null) {
|
|
return o
|
|
} else {
|
|
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
|
|
|
|
function generateUserId () {
|
|
if (typeof crypto !== 'undefined' && crypto.getRandomValue != null) {
|
|
// browser
|
|
let arr = new Uint32Array(1)
|
|
crypto.getRandomValues(arr)
|
|
return arr[0]
|
|
} else if (typeof crypto !== 'undefined' && crypto.randomBytes != null) {
|
|
// node
|
|
let buf = crypto.randomBytes(4)
|
|
return new Uint32Array(buf.buffer)[0]
|
|
} else {
|
|
return Math.ceil(Math.random() * 0xFFFFFFFF)
|
|
}
|
|
}
|
|
Y.utils.generateUserId = generateUserId
|
|
|
|
Y.utils.parseTypeDefinition = function parseTypeDefinition (type, typeArgs) {
|
|
var args = []
|
|
try {
|
|
args = JSON.parse('[' + typeArgs + ']')
|
|
} catch (e) {
|
|
throw new Error('Was not able to parse type definition!')
|
|
}
|
|
if (type.typeDefinition.parseArguments != null) {
|
|
args = type.typeDefinition.parseArguments(args[0])[1]
|
|
}
|
|
return args
|
|
}
|
|
}
|