after refactor - some tests are working again

This commit is contained in:
Kevin Jahns 2019-04-05 00:37:09 +02:00
parent 30bf3742c9
commit e56899a02c
23 changed files with 234 additions and 152 deletions

View File

@ -85,7 +85,8 @@ export default [{
}), }),
commonjs() commonjs()
] ]
}, { }
/* {
input: ['./examples/codemirror.js', './examples/textarea.js', './examples/quill.js', './examples/dom.js', './examples/prosemirror.js'], // fs.readdirSync('./examples').filter(file => /(?<!\.(test|config))\.js$/.test(file)).map(file => './examples/' + file), input: ['./examples/codemirror.js', './examples/textarea.js', './examples/quill.js', './examples/dom.js', './examples/prosemirror.js'], // fs.readdirSync('./examples').filter(file => /(?<!\.(test|config))\.js$/.test(file)).map(file => './examples/' + file),
output: { output: {
dir: 'examples/build', dir: 'examples/build',
@ -102,4 +103,4 @@ export default [{
commonjs(), commonjs(),
...minificationPlugins ...minificationPlugins
] ]
}] } */]

View File

@ -11,6 +11,7 @@ import {
addStruct, addStruct,
addToDeleteSet, addToDeleteSet,
ItemDeleted, ItemDeleted,
findRootTypeKey,
ID, AbstractType, Y, Transaction // eslint-disable-line ID, AbstractType, Y, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@ -229,7 +230,7 @@ export class AbstractItem extends AbstractStruct {
maplib.setIfUndefined(transaction.changed, parent, set.create).add(parentSub) maplib.setIfUndefined(transaction.changed, parent, set.create).add(parentSub)
} }
// @ts-ignore // @ts-ignore
if (parent._item.deleted || (left !== null && parentSub !== null)) { if ((parent._item !== null && parent._item.deleted) || (left !== null && parentSub !== null)) {
// delete if parent is deleted or if this is not the current attribute value of parent // delete if parent is deleted or if this is not the current attribute value of parent
this.delete(transaction) this.delete(transaction)
} else if (parentSub !== null && left === null && right !== null) { } else if (parentSub !== null && left === null && right !== null) {
@ -443,16 +444,12 @@ export class AbstractItem extends AbstractStruct {
const info = (encodingRef & binary.BITS5) | const info = (encodingRef & binary.BITS5) |
((this.origin === null) ? 0 : binary.BIT8) | // origin is defined ((this.origin === null) ? 0 : binary.BIT8) | // origin is defined
((this.rightOrigin === null) ? 0 : binary.BIT7) | // right origin is defined ((this.rightOrigin === null) ? 0 : binary.BIT7) | // right origin is defined
((this.parentSub !== null) ? 0 : binary.BIT6) // parentSub is non-null ((this.parentSub === null) ? 0 : binary.BIT6) // parentSub is non-null
encoding.writeUint8(encoder, info) encoding.writeUint8(encoder, info)
if (offset === 0) { if (this.origin !== null) {
writeID(encoder, this.id) if (offset === 0) {
if (this.origin !== null) {
writeID(encoder, this.origin.lastId) writeID(encoder, this.origin.lastId)
} } else {
} else {
writeID(encoder, createID(this.id.client, this.id.clock + offset))
if (this.origin !== null) {
writeID(encoder, createID(this.id.client, this.id.clock + offset - 1)) writeID(encoder, createID(this.id.client, this.id.clock + offset - 1))
} }
} }
@ -465,17 +462,7 @@ export class AbstractItem extends AbstractStruct {
// parent type on y._map // parent type on y._map
// find the correct key // find the correct key
// @ts-ignore we know that y exists // @ts-ignore we know that y exists
const map = parent._y.share const ykey = findRootTypeKey(this.parent)
let ykey = null
for (const [key, type] of map) {
if (type === parent) {
ykey = key
break
}
}
if (ykey === null) {
throw error.unexpectedCase()
}
encoding.writeVarUint(encoder, 1) // write parentYKey encoding.writeVarUint(encoder, 1) // write parentYKey
encoding.writeVarString(encoder, ykey) encoding.writeVarString(encoder, ykey)
} else { } else {
@ -509,7 +496,7 @@ export class AbstractItemRef extends AbstractRef {
*/ */
this.right = (info & binary.BIT7) === binary.BIT7 ? readID(decoder) : null this.right = (info & binary.BIT7) === binary.BIT7 ? readID(decoder) : null
const canCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0 const canCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0
const hasParentYKey = decoding.readVarUint(decoder) === 1 const hasParentYKey = canCopyParentInfo ? decoding.readVarUint(decoder) === 1 : false
/** /**
* If parent = null and neither left nor right are defined, then we know that `parent` is child of `y` * If parent = null and neither left nor right are defined, then we know that `parent` is child of `y`
* and we read the next string as parentYKey. * and we read the next string as parentYKey.
@ -541,16 +528,4 @@ export class AbstractItemRef extends AbstractRef {
missing.push(this.parent) missing.push(this.parent)
} }
} }
/**
* @param {Transaction} transaction
* @return {Array<ID|null>}
*/
getMissing (transaction) {
return [
createID(this.id.client, this.id.clock - 1),
this.left,
this.right,
this.parent
]
}
} }

View File

@ -18,6 +18,7 @@ export class AbstractStruct {
* @readonly * @readonly
*/ */
this.id = id this.id = id
this.deleted = false
} }
/** /**
* Merge this struct with the item to the right. * Merge this struct with the item to the right.
@ -35,12 +36,6 @@ export class AbstractStruct {
get length () { get length () {
throw error.methodUnimplemented() throw error.methodUnimplemented()
} }
/**
* @type {boolean}
*/
get deleted () {
throw error.methodUnimplemented()
}
/** /**
* @param {encoding.Encoder} encoder The encoder to write data to. * @param {encoding.Encoder} encoder The encoder to write data to.
* @param {number} offset * @param {number} offset
@ -87,4 +82,10 @@ export class AbstractRef {
toStruct (transaction) { toStruct (transaction) {
throw error.methodUnimplemented() throw error.methodUnimplemented()
} }
/**
* @type {number}
*/
get length () {
return 1
}
} }

View File

@ -25,11 +25,12 @@ export class GC extends AbstractStruct {
/** /**
* @type {number} * @type {number}
*/ */
this.length = length this._len = length
this.deleted = true
} }
get deleted () { get length () {
return true return this._len
} }
/** /**
@ -37,7 +38,7 @@ export class GC extends AbstractStruct {
* @return {boolean} * @return {boolean}
*/ */
mergeWith (right) { mergeWith (right) {
this.length += right.length this._len += right.length
return true return true
} }
@ -52,7 +53,7 @@ export class GC extends AbstractStruct {
} else { } else {
writeID(encoder, createID(this.id.client, this.id.clock + offset)) writeID(encoder, createID(this.id.client, this.id.clock + offset))
} }
encoding.writeVarUint(encoder, this.length) encoding.writeVarUint(encoder, this._len)
} }
} }
@ -71,7 +72,10 @@ export class GCRef extends AbstractRef {
/** /**
* @type {number} * @type {number}
*/ */
this.length = decoding.readVarUint(decoder) this._len = decoding.readVarUint(decoder)
}
get length () {
return this._len
} }
missing () { missing () {
return [ return [
@ -84,7 +88,7 @@ export class GCRef extends AbstractRef {
toStruct () { toStruct () {
return new GC( return new GC(
this.id, this.id,
this.length this._len
) )
} }
} }

View File

@ -29,7 +29,11 @@ export class ItemDeleted extends AbstractItem {
*/ */
constructor (id, left, right, parent, parentSub, length) { constructor (id, left, right, parent, parentSub, length) {
super(id, left, right, parent, parentSub) super(id, left, right, parent, parentSub)
this.length = length this._len = length
this.deleted = true
}
get length () {
return this._len
} }
/** /**
* @param {ID} id * @param {ID} id
@ -47,7 +51,7 @@ export class ItemDeleted extends AbstractItem {
*/ */
mergeWith (right) { mergeWith (right) {
if (right.origin === this && this.right === right) { if (right.origin === this && this.right === right) {
this.length += right.length this._len += right.length
return true return true
} }
return false return false
@ -73,7 +77,10 @@ export class ItemDeletedRef extends AbstractItemRef {
/** /**
* @type {number} * @type {number}
*/ */
this.length = decoding.readVarUint(decoder) this.len = decoding.readVarUint(decoder)
}
get length () {
return this.len
} }
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
@ -89,7 +96,7 @@ export class ItemDeletedRef extends AbstractItemRef {
// @ts-ignore // @ts-ignore
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent).type, this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent).type,
this.parentSub, this.parentSub,
this.length this.len
) )
} }
} }

View File

@ -74,7 +74,7 @@ export class ItemEmbedRef extends AbstractItemRef {
this.left === null ? null : getItemCleanEnd(store, transaction, this.left), this.left === null ? null : getItemCleanEnd(store, transaction, this.left),
this.right === null ? null : getItemCleanStart(store, transaction, this.right), this.right === null ? null : getItemCleanStart(store, transaction, this.right),
// @ts-ignore // @ts-ignore
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent), this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent).type,
this.parentSub, this.parentSub,
this.embed this.embed
) )

View File

@ -81,7 +81,7 @@ export class ItemFormatRef extends AbstractItemRef {
this.left === null ? null : getItemCleanEnd(store, transaction, this.left), this.left === null ? null : getItemCleanEnd(store, transaction, this.left),
this.right === null ? null : getItemCleanStart(store, transaction, this.right), this.right === null ? null : getItemCleanStart(store, transaction, this.right),
// @ts-ignore // @ts-ignore
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent), this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent).type,
this.parentSub, this.parentSub,
this.key, this.key,
this.value this.value

View File

@ -108,6 +108,9 @@ export class ItemJSONRef extends AbstractItemRef {
} }
this.content = cs this.content = cs
} }
get length () {
return this.content.length
}
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @return {ItemJSON} * @return {ItemJSON}
@ -120,7 +123,7 @@ export class ItemJSONRef extends AbstractItemRef {
this.left === null ? null : getItemCleanEnd(store, transaction, this.left), this.left === null ? null : getItemCleanEnd(store, transaction, this.left),
this.right === null ? null : getItemCleanStart(store, transaction, this.right), this.right === null ? null : getItemCleanStart(store, transaction, this.right),
// @ts-ignore // @ts-ignore
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent), this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent).type,
this.parentSub, this.parentSub,
this.content this.content
) )

View File

@ -97,6 +97,9 @@ export class ItemStringRef extends AbstractItemRef {
*/ */
this.string = decoding.readVarString(decoder) this.string = decoding.readVarString(decoder)
} }
get length () {
return this.string.length
}
/** /**
* @param {Transaction} transaction * @param {Transaction} transaction
* @return {ItemString} * @return {ItemString}
@ -109,7 +112,7 @@ export class ItemStringRef extends AbstractItemRef {
this.left === null ? null : getItemCleanEnd(store, transaction, this.left), this.left === null ? null : getItemCleanEnd(store, transaction, this.left),
this.right === null ? null : getItemCleanStart(store, transaction, this.right), this.right === null ? null : getItemCleanStart(store, transaction, this.right),
// @ts-ignore // @ts-ignore
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent), this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent).type,
this.parentSub, this.parentSub,
this.string this.string
) )

View File

@ -49,6 +49,14 @@ export const typeRefs = [
readYXmlText readYXmlText
] ]
export const YArrayRefID = 0
export const YMapRefID = 1
export const YTextRefID = 2
export const YXmlElementRefID = 3
export const YXmlFragmentRefID = 4
export const YXmlHookRefID = 5
export const YXmlTextRefID = 6
export class ItemType extends AbstractItem { export class ItemType extends AbstractItem {
/** /**
* @param {ID} id * @param {ID} id
@ -62,6 +70,7 @@ export class ItemType extends AbstractItem {
super(id, left, right, parent, parentSub) super(id, left, right, parent, parentSub)
this.type = type this.type = type
} }
getContent () { getContent () {
return [this.type] return [this.type]
} }
@ -80,7 +89,8 @@ export class ItemType extends AbstractItem {
* @param {Transaction} transaction * @param {Transaction} transaction
*/ */
integrate (transaction) { integrate (transaction) {
this.type._integrate(transaction, this) this.type._integrate(transaction.y, this)
super.integrate(transaction)
} }
/** /**
* @param {encoding.Encoder} encoder * @param {encoding.Encoder} encoder
@ -168,7 +178,7 @@ export class ItemTypeRef extends AbstractItemRef {
this.left === null ? null : getItemCleanEnd(store, transaction, this.left), this.left === null ? null : getItemCleanEnd(store, transaction, this.left),
this.right === null ? null : getItemCleanStart(store, transaction, this.right), this.right === null ? null : getItemCleanStart(store, transaction, this.right),
// @ts-ignore // @ts-ignore
this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent), this.parent === null ? y.get(this.parentYKey) : getItemType(store, this.parent).type,
this.parentSub, this.parentSub,
this.type this.type
) )

View File

@ -68,12 +68,12 @@ export class AbstractType {
* * This type is sent to other client * * This type is sent to other client
* * Observer functions are fired * * Observer functions are fired
* *
* @param {Transaction} transaction The Yjs instance * @param {Y} y The Yjs instance
* @param {ItemType} item * @param {ItemType|null} item
* @private * @private
*/ */
_integrate (transaction, item) { _integrate (y, item) {
this._y = transaction.y this._y = y
this._item = item this._item = item
} }
@ -87,9 +87,7 @@ export class AbstractType {
/** /**
* @param {encoding.Encoder} encoder * @param {encoding.Encoder} encoder
*/ */
_write (encoder) { _write (encoder) { }
throw new Error('unimplemented')
}
/** /**
* The first non-deleted item * The first non-deleted item
@ -329,12 +327,19 @@ export const typeArrayGet = (type, index) => {
* @param {Array<Object<string,any>|Array<any>|number|string|ArrayBuffer>} content * @param {Array<Object<string,any>|Array<any>|number|string|ArrayBuffer>} content
*/ */
export const typeArrayInsertGenericsAfter = (transaction, parent, referenceItem, content) => { export const typeArrayInsertGenericsAfter = (transaction, parent, referenceItem, content) => {
let left = referenceItem const left = referenceItem
const right = referenceItem === null ? parent._start : referenceItem.right const right = referenceItem === null ? parent._start : referenceItem.right
/** /**
* @type {Array<Object|Array|number>} * @type {Array<Object|Array|number>}
*/ */
let jsonContent = [] let jsonContent = []
const packJsonContent = () => {
if (jsonContent.length > 0) {
const item = new ItemJSON(nextID(transaction), left, right, parent, null, jsonContent)
item.integrate(transaction)
jsonContent = []
}
}
content.forEach(c => { content.forEach(c => {
switch (c.constructor) { switch (c.constructor) {
case Number: case Number:
@ -344,11 +349,7 @@ export const typeArrayInsertGenericsAfter = (transaction, parent, referenceItem,
jsonContent.push(c) jsonContent.push(c)
break break
default: default:
if (jsonContent.length > 0) { packJsonContent()
const item = new ItemJSON(nextID(transaction), left, right, parent, null, jsonContent)
item.integrate(transaction)
jsonContent = []
}
switch (c.constructor) { switch (c.constructor) {
case ArrayBuffer: case ArrayBuffer:
// @ts-ignore c is definitely an ArrayBuffer // @ts-ignore c is definitely an ArrayBuffer
@ -363,6 +364,7 @@ export const typeArrayInsertGenericsAfter = (transaction, parent, referenceItem,
} }
} }
}) })
packJsonContent()
} }
/** /**
@ -373,20 +375,22 @@ export const typeArrayInsertGenericsAfter = (transaction, parent, referenceItem,
*/ */
export const typeArrayInsertGenerics = (transaction, parent, index, content) => { export const typeArrayInsertGenerics = (transaction, parent, index, content) => {
if (index === 0) { if (index === 0) {
typeArrayInsertGenericsAfter(transaction, parent, null, content) return typeArrayInsertGenericsAfter(transaction, parent, null, content)
} }
for (let n = parent._start; n !== null; n = n.right) { let n = parent._start
for (; n !== null; n = n.right) {
if (!n.deleted && n.countable) { if (!n.deleted && n.countable) {
if (index <= n.length) { if (index <= n.length) {
if (index < n.length) { if (index < n.length) {
// insert in-between
getItemCleanStart(transaction.y.store, transaction, createID(n.id.client, n.id.clock + index)) getItemCleanStart(transaction.y.store, transaction, createID(n.id.client, n.id.clock + index))
} }
return typeArrayInsertGenericsAfter(transaction, parent, n, content) break
} }
index -= n.length index -= n.length
} }
} }
throw new Error('Index exceeds array range') return typeArrayInsertGenericsAfter(transaction, parent, n, content)
} }
/** /**
@ -440,6 +444,10 @@ export const typeMapDelete = (transaction, parent, key) => {
*/ */
export const typeMapSet = (transaction, parent, key, value) => { export const typeMapSet = (transaction, parent, key, value) => {
const right = parent._map.get(key) || null const right = parent._map.get(key) || null
if (value == null) {
new ItemJSON(nextID(transaction), null, right, parent, key, [value]).integrate(transaction)
return
}
switch (value.constructor) { switch (value.constructor) {
case Number: case Number:
case Object: case Object:

View File

@ -12,10 +12,12 @@ import {
typeArrayInsertGenerics, typeArrayInsertGenerics,
typeArrayDelete, typeArrayDelete,
typeArrayMap, typeArrayMap,
Transaction, ItemType, // eslint-disable-line YArrayRefID,
Y, Transaction, ItemType, // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as decoding from 'lib0/decoding.js' // eslint-disable-line import * as decoding from 'lib0/decoding.js' // eslint-disable-line
import * as encoding from 'lib0/encoding.js'
/** /**
* Event that describes the changes on a YArray * Event that describes the changes on a YArray
@ -52,12 +54,12 @@ export class YArray extends AbstractType {
* * This type is sent to other client * * This type is sent to other client
* * Observer functions are fired * * Observer functions are fired
* *
* @param {Transaction} transaction The Yjs instance * @param {Y} y The Yjs instance
* @param {ItemType} item * @param {ItemType} item
* @private * @private
*/ */
_integrate (transaction, item) { _integrate (y, item) {
super._integrate(transaction, item) super._integrate(y, item)
// @ts-ignore // @ts-ignore
this.insert(0, this._prelimContent) this.insert(0, this._prelimContent)
this._prelimContent = null this._prelimContent = null
@ -183,6 +185,13 @@ export class YArray extends AbstractType {
push (content) { push (content) {
this.insert(this.length, content) this.insert(this.length, content)
} }
/**
* @param {encoding.Encoder} encoder
*/
_write (encoder) {
encoding.writeVarUint(encoder, YArrayRefID)
}
} }
/** /**

View File

@ -10,9 +10,11 @@ import {
typeMapGet, typeMapGet,
typeMapHas, typeMapHas,
createMapIterator, createMapIterator,
Transaction, ItemType, // eslint-disable-line YMapRefID,
Y, Transaction, ItemType, // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js' // eslint-disable-line import * as decoding from 'lib0/decoding.js' // eslint-disable-line
import * as iterator from 'lib0/iterator.js' import * as iterator from 'lib0/iterator.js'
@ -53,12 +55,12 @@ export class YMap extends AbstractType {
* * This type is sent to other client * * This type is sent to other client
* * Observer functions are fired * * Observer functions are fired
* *
* @param {Transaction} transaction The Yjs instance * @param {Y} y The Yjs instance
* @param {ItemType} item * @param {ItemType} item
* @private * @private
*/ */
_integrate (transaction, item) { _integrate (y, item) {
super._integrate(transaction, item) super._integrate(y, item)
// @ts-ignore // @ts-ignore
for (let [key, value] of this._prelimContent) { for (let [key, value] of this._prelimContent) {
this.set(key, value) this.set(key, value)
@ -88,7 +90,8 @@ export class YMap extends AbstractType {
const map = {} const map = {}
for (let [key, item] of this._map) { for (let [key, item] of this._map) {
if (!item.deleted) { if (!item.deleted) {
map[key] = item.getContent()[0] const v = item.getContent()[0]
map[key] = v instanceof AbstractType ? v.toJSON() : v
} }
} }
return map return map
@ -169,6 +172,13 @@ export class YMap extends AbstractType {
has (key) { has (key) {
return typeMapHas(this, key) return typeMapHas(this, key)
} }
/**
* @param {encoding.Encoder} encoder
*/
_write (encoder) {
encoding.writeVarUint(encoder, YMapRefID)
}
} }
/** /**

View File

@ -12,10 +12,12 @@ import {
createID, createID,
getItemCleanStart, getItemCleanStart,
isVisible, isVisible,
ItemType, AbstractItem, Snapshot, StructStore, Transaction // eslint-disable-line YTextRefID,
Y, ItemType, AbstractItem, Snapshot, StructStore, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as decoding from 'lib0/decoding.js' // eslint-disable-line import * as decoding from 'lib0/decoding.js' // eslint-disable-line
import * as encoding from 'lib0/encoding.js'
/** /**
* @private * @private
@ -566,11 +568,11 @@ export class YText extends AbstractType {
} }
/** /**
* @param {Transaction} transaction * @param {Y} y
* @param {ItemType} item * @param {ItemType} item
*/ */
_integrate (transaction, item) { _integrate (y, item) {
super._integrate(transaction, item) super._integrate(y, item)
// @ts-ignore this._prelimContent is still defined // @ts-ignore this._prelimContent is still defined
this.insert(0, this._prelimContent.join('')) this.insert(0, this._prelimContent.join(''))
this._prelimContent = null this._prelimContent = null
@ -839,6 +841,13 @@ export class YText extends AbstractType {
}) })
} }
} }
/**
* @param {encoding.Encoder} encoder
*/
_write (encoder) {
encoding.writeVarUint(encoder, YTextRefID)
}
} }
/** /**

View File

@ -13,7 +13,8 @@ import {
typeArrayDelete, typeArrayDelete,
typeMapSet, typeMapSet,
typeMapDelete, typeMapDelete,
Transaction, ItemType, YXmlText, YXmlHook, Snapshot // eslint-disable-line YXmlElementRefID,
Y, Transaction, ItemType, YXmlText, YXmlHook, Snapshot // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js' import * as encoding from 'lib0/encoding.js'
@ -259,12 +260,12 @@ export class YXmlElement extends YXmlFragment {
* * This type is sent to other client * * This type is sent to other client
* * Observer functions are fired * * Observer functions are fired
* *
* @param {Transaction} transaction The Yjs instance * @param {Y} y The Yjs instance
* @param {ItemType} item * @param {ItemType} item
* @private * @private
*/ */
_integrate (transaction, item) { _integrate (y, item) {
super._integrate(transaction, item) super._integrate(y, item)
// @ts-ignore // @ts-ignore
this.insert(0, this._prelimContent) this.insert(0, this._prelimContent)
this._prelimContent = null this._prelimContent = null
@ -285,19 +286,6 @@ export class YXmlElement extends YXmlFragment {
return new YXmlElement(this.nodeName) return new YXmlElement(this.nodeName)
} }
/**
* Transform the properties of this type to binary and write it to an
* BinaryEncoder.
*
* This is called when this Item is sent to a remote peer.
*
* @private
* @param {encoding.Encoder} encoder The encoder to write data to.
*/
_write (encoder) {
encoding.writeVarString(encoder, this.nodeName)
}
toString () { toString () {
return this.toDomString() return this.toDomString()
} }
@ -460,6 +448,20 @@ export class YXmlElement extends YXmlFragment {
} }
return dom return dom
} }
/**
* Transform the properties of this type to binary and write it to an
* BinaryEncoder.
*
* This is called when this Item is sent to a remote peer.
*
* @private
* @param {encoding.Encoder} encoder The encoder to write data to.
*/
_write (encoder) {
encoding.writeVarUint(encoder, YXmlElementRefID)
encoding.writeVarString(encoder, this.nodeName)
}
} }
/** /**

View File

@ -2,7 +2,10 @@
* @module types * @module types
*/ */
import { YMap } from '../internals.js' import {
YMap,
YXmlHookRefID
} from '../internals.js'
import * as encoding from 'lib0/encoding.js' import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js' import * as decoding from 'lib0/decoding.js'
@ -71,6 +74,7 @@ export class YXmlHook extends YMap {
*/ */
_write (encoder) { _write (encoder) {
super._write(encoder) super._write(encoder)
encoding.writeVarUint(encoder, YXmlHookRefID)
encoding.writeVarString(encoder, this.hookName) encoding.writeVarString(encoder, this.hookName)
} }
} }

View File

@ -2,8 +2,9 @@
* @module types * @module types
*/ */
import { YText } from '../internals.js' import { YText, YXmlTextRefID } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js' // eslint-disable-line import * as decoding from 'lib0/decoding.js' // eslint-disable-line
/** /**
@ -33,6 +34,12 @@ export class YXmlText extends YText {
} }
return dom return dom
} }
/**
* @param {encoding.Encoder} encoder
*/
_write (encoder) {
encoding.writeVarUint(encoder, YXmlTextRefID)
}
} }
/** /**

View File

@ -88,6 +88,9 @@ export const findIndexSS = (structs, clock) => {
if (clock < midclock + mid.length) { if (clock < midclock + mid.length) {
return midindex return midindex
} }
if (left === midindex) {
throw error.unexpectedCase()
}
left = midindex left = midindex
} else { } else {
right = midindex right = midindex

View File

@ -9,6 +9,7 @@ import {
writeDeleteSet, writeDeleteSet,
DeleteSet, DeleteSet,
sortAndMergeDeleteSet, sortAndMergeDeleteSet,
getStates,
AbstractType, AbstractItem, YEvent, ItemType, Y // eslint-disable-line AbstractType, AbstractItem, YEvent, ItemType, Y // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@ -52,12 +53,12 @@ export class Transaction {
*/ */
this.deleteSet = new DeleteSet() this.deleteSet = new DeleteSet()
/** /**
* If a state was modified, the original value is saved here. * Holds the state before the transaction started.
* Use `stateUpdates` to compute the original state before the transaction,
* or to compute the set of inserted operations.
* @type {Map<Number,Number>} * @type {Map<Number,Number>}
*/ */
this.stateUpdates = new Map() this.beforeState = new Map()
getStates(y.store).forEach(({client, clock}) => { this.beforeState.set(client, clock) })
this.afterState = new Map()
/** /**
* All types that were directly modified (property added or child * All types that were directly modified (property added or child
* inserted/deleted). New types are not included in this Set. * inserted/deleted). New types are not included in this Set.

View File

@ -1,4 +1,4 @@
import { } from './StructStore.js' import { getStates } from './StructStore.js'
import { import {
callEventHandlerListeners, callEventHandlerListeners,
@ -75,26 +75,29 @@ export class Y extends Observable {
if (initialCall) { if (initialCall) {
const transaction = this._transaction const transaction = this._transaction
this._transaction = null this._transaction = null
// only call event listeners / observers if anything changed this.emit('beforeObserverCalls', [this, this._transaction])
// emit change events on changed types
transaction.changed.forEach((subs, itemtype) => {
itemtype._callObserver(transaction, subs)
})
transaction.changedParentTypes.forEach((events, type) => {
events = events
.filter(event =>
event.target._item === null || !event.target._item.deleted
)
events
.forEach(event => {
event.currentTarget = type
})
// we don't need to check for events.length
// because we know it has at least one element
callEventHandlerListeners(type._dEH, [events, transaction])
})
// only call afterTransaction listeners if anything changed
const transactionChangedContent = transaction.changedParentTypes.size !== 0 const transactionChangedContent = transaction.changedParentTypes.size !== 0
if (transactionChangedContent) { if (transactionChangedContent) {
this.emit('beforeObserverCalls', [this, this._transaction]) getStates(transaction.y.store).forEach(({client, clock}) => {
// emit change events on changed types transaction.afterState.set(client, clock)
transaction.changed.forEach((subs, itemtype) => {
itemtype._callObserver(transaction, subs)
})
transaction.changedParentTypes.forEach((events, type) => {
events = events
.filter(event =>
event.target._item === null || !event.target._item.deleted
)
events
.forEach(event => {
event.currentTarget = type
})
// we don't need to check for events.length
// because we know it has at least one element
callEventHandlerListeners(type._dEH, [events, transaction])
}) })
// when all changes & events are processed, emit afterTransaction event // when all changes & events are processed, emit afterTransaction event
this.emit('afterTransaction', [this, transaction]) this.emit('afterTransaction', [this, transaction])
@ -141,15 +144,17 @@ export class Y extends Observable {
} }
} }
// on all affected store.clients props, try to merge // on all affected store.clients props, try to merge
for (const [client, clock] of transaction.stateUpdates) { for (const [client, clock] of transaction.beforeState) {
/** if (transaction.afterState.get(client) !== clock) {
* @type {Array<AbstractStruct>} /**
*/ * @type {Array<AbstractStruct>}
// @ts-ignore */
const structs = store.clients.get(client) // @ts-ignore
// we iterate from right to left so we can safely remove entries const structs = store.clients.get(client)
for (let i = structs.length - 1; i >= math.max(findIndexSS(structs, clock), 1); i--) { // we iterate from right to left so we can safely remove entries
tryToMergeWithLeft(structs, i) for (let i = structs.length - 1; i >= math.max(findIndexSS(structs, clock), 1); i--) {
tryToMergeWithLeft(structs, i)
}
} }
} }
// try to merge replacedItems // try to merge replacedItems
@ -200,16 +205,21 @@ export class Y extends Observable {
* @return {AbstractType<any>} The created type. Constructed with TypeConstructor * @return {AbstractType<any>} The created type. Constructed with TypeConstructor
*/ */
get (name, TypeConstructor = AbstractType) { get (name, TypeConstructor = AbstractType) {
// @ts-ignore const type = map.setIfUndefined(this.share, name, () => {
const type = map.setIfUndefined(this.share, name, () => new TypeConstructor()) // @ts-ignore
const t = new TypeConstructor()
t._integrate(this, null)
return t
})
const Constr = type.constructor const Constr = type.constructor
if (Constr !== TypeConstructor) { if (TypeConstructor !== AbstractType && Constr !== TypeConstructor) {
if (Constr === AbstractType) { if (Constr === AbstractType) {
const t = new Constr() const t = new Constr()
t._map = type._map t._map = type._map
t._start = type._start t._start = type._start
t._length = type._length t._length = type._length
this.share.set(name, t) this.share.set(name, t)
t._integrate(this, null)
return t return t
} else { } else {
throw new Error(`Type with the name ${name} has already been defined with a different constructor`) throw new Error(`Type with the name ${name} has already been defined with a different constructor`)

View File

@ -67,7 +67,7 @@ export class YEvent {
* @return {boolean} * @return {boolean}
*/ */
adds (struct) { adds (struct) {
return struct.id.clock > (this.transaction.stateUpdates.get(struct.id.client) || 0) return struct.id.clock > (this.transaction.beforeState.get(struct.id.client) || 0)
} }
} }

View File

@ -13,6 +13,8 @@ import {
writeID, writeID,
createID, createID,
readID, readID,
getState,
getStates,
Transaction, AbstractStruct, AbstractRef, StructStore, ID // eslint-disable-line Transaction, AbstractStruct, AbstractRef, StructStore, ID // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@ -51,7 +53,8 @@ const createStructReaderIterator = (decoder, structsLen, nextID) => iterator.cre
} else { } else {
const info = decoding.readUint8(decoder) const info = decoding.readUint8(decoder)
value = new structRefs[binary.BITS5 & info](decoder, nextID, info) value = new structRefs[binary.BITS5 & info](decoder, nextID, info)
nextID = createID(nextID.client, nextID.clock) nextID = createID(nextID.client, nextID.clock + value.length)
structsLen--
} }
return { done, value } return { done, value }
}) })
@ -60,18 +63,30 @@ const createStructReaderIterator = (decoder, structsLen, nextID) => iterator.cre
* @param {encoding.Encoder} encoder * @param {encoding.Encoder} encoder
* @param {Transaction} transaction * @param {Transaction} transaction
*/ */
export const writeStructsFromTransaction = (encoder, transaction) => writeStructs(encoder, transaction.y.store, transaction.stateUpdates) export const writeStructsFromTransaction = (encoder, transaction) => writeStructs(encoder, transaction.y.store, transaction.beforeState)
/** /**
* @param {encoding.Encoder} encoder * @param {encoding.Encoder} encoder
* @param {StructStore} store * @param {StructStore} store
* @param {StateMap} sm * @param {StateMap} _sm
*/ */
export const writeStructs = (encoder, store, sm) => { export const writeStructs = (encoder, store, _sm) => {
// we filter all valid _sm entries into sm
const sm = new Map()
_sm.forEach((clock, client) => {
if (getState(store, client) > clock) {
sm.set(client, clock)
}
})
getStates(store).forEach(({client}) => {
if (!_sm.has(client)) {
sm.set(client, 0)
}
})
const encoderUserPosMap = map.create() const encoderUserPosMap = map.create()
// write # states that were updated // write # states that were updated
encoding.writeVarUint(encoder, sm.size) encoding.writeVarUint(encoder, sm.size)
sm.forEach((client, clock) => { sm.forEach((clock, client) => {
// write first id // write first id
writeID(encoder, createID(client, clock)) writeID(encoder, createID(client, clock))
encoderUserPosMap.set(client, encoding.length(encoder)) encoderUserPosMap.set(client, encoding.length(encoder))
@ -79,7 +94,7 @@ export const writeStructs = (encoder, store, sm) => {
// We will fill out this value later *) // We will fill out this value later *)
encoding.writeUint32(encoder, 0) encoding.writeUint32(encoder, 0)
}) })
sm.forEach((client, clock) => { sm.forEach((clock, client) => {
const decPos = encoderUserPosMap.get(client) const decPos = encoderUserPosMap.get(client)
encoding.setUint32(encoder, decPos, encoding.length(encoder) - decPos) encoding.setUint32(encoder, decPos, encoding.length(encoder) - decPos)
/** /**
@ -115,8 +130,8 @@ export const readStructs = (decoder, transaction, store) => {
* @type {Map<number,Iterator<AbstractRef>>} * @type {Map<number,Iterator<AbstractRef>>}
*/ */
const structReaders = new Map() const structReaders = new Map()
const clientStateUpdates = decoding.readVarUint(decoder) const clientbeforeState = decoding.readVarUint(decoder)
for (let i = 0; i < clientStateUpdates; i++) { for (let i = 0; i < clientbeforeState; i++) {
const nextID = readID(decoder) const nextID = readID(decoder)
const decoderPos = decoder.pos + decoding.readUint32(decoder) const decoderPos = decoder.pos + decoding.readUint32(decoder)
const structReaderDecoder = decoding.clone(decoder, decoderPos) const structReaderDecoder = decoding.clone(decoder, decoderPos)

View File

@ -268,7 +268,7 @@ export const compare = users => {
while (users[0].tc.flushAllMessages()) {} while (users[0].tc.flushAllMessages()) {}
const userArrayValues = users.map(u => u.getArray('array').toJSON().map(val => JSON.stringify(val))) const userArrayValues = users.map(u => u.getArray('array').toJSON().map(val => JSON.stringify(val)))
const userMapValues = users.map(u => u.getMap('map').toJSON()) const userMapValues = users.map(u => u.getMap('map').toJSON())
const userXmlValues = users.map(u => u.getXmlFragment('xml').toString()) const userXmlValues = users.map(u => u.get('xml', Y.XmlElement).toString())
const userTextValues = users.map(u => u.getText('text').toDelta()) const userTextValues = users.map(u => u.getText('text').toDelta())
for (var i = 0; i < users.length - 1; i++) { for (var i = 0; i < users.length - 1; i++) {
t.describe(`Comparing user${i} with user${i + 1}`) t.describe(`Comparing user${i} with user${i + 1}`)