Implement experimental new encoder 🚀

This commit is contained in:
Kevin Jahns 2020-07-12 18:25:45 +02:00
parent e31e968f0d
commit 6c2cf0f769
36 changed files with 1224 additions and 336 deletions

View File

@ -17,10 +17,13 @@
"useCollapsibles": true, "useCollapsibles": true,
"collapse": true, "collapse": true,
"resources": { "resources": {
"yjs.dev": "Yjs website" "yjs.dev": "Website",
"docs.yjs.dev": "Docs",
"discuss.yjs.dev": "Forum",
"https://gitter.im/Yjs/community": "Chat"
}, },
"logo": { "logo": {
"url": "https://user-images.githubusercontent.com/5553757/48975307-61efb100-f06d-11e8-9177-ee895e5916e5.png", "url": "https://yjs.dev/images/logo/yjs-512x512.png",
"width": "162px", "width": "162px",
"height": "162px", "height": "162px",
"link": "/" "link": "/"
@ -35,7 +38,7 @@
], ],
"default": { "default": {
"staticFiles": { "staticFiles": {
"include": ["examples/"] "include": []
} }
} }
}, },
@ -44,7 +47,6 @@
"encoding": "utf8", "encoding": "utf8",
"private": false, "private": false,
"recurse": true, "recurse": true,
"template": "./node_modules/tui-jsdoc-template", "template": "./node_modules/tui-jsdoc-template"
"tutorials": "./examples"
} }
} }

View File

@ -958,5 +958,6 @@ Yjs and all related projects are [**MIT licensed**](./LICENSE).
Yjs is based on my research as a student at the [RWTH Yjs is based on my research as a student at the [RWTH
i5](http://dbis.rwth-aachen.de/). Now I am working on Yjs in my spare time. i5](http://dbis.rwth-aachen.de/). Now I am working on Yjs in my spare time.
Fund this project by donating on [Patreon](https://www.patreon.com/dmonad) or Fund this project by donating on [GitHub Sponsors](https://github.com/sponsors/dmonad)
hiring [me](https://github.com/dmonad) for professional support. or hiring [me](https://github.com/dmonad) as a contractor for your collaborative
app.

40
package-lock.json generated
View File

@ -1429,9 +1429,9 @@
"dev": true "dev": true
}, },
"isomorphic.js": { "isomorphic.js": {
"version": "0.1.3", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.1.3.tgz", "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.1.4.tgz",
"integrity": "sha512-pabBRLDwYefSsNS+qCazJ97o7P5xDTrNoxSYFTM09JlZTxPrOEPGKekwqUy3/Np4C4PHnVUXHYsZPOix0jELsA==" "integrity": "sha512-t9zbgkjE7f9f2M6OSW49YEq0lUrSdAllBbWFUZoeck/rnnFae6UlhmDtXWs48VJY3ZpryCoZsRiAiKD44hPIGQ=="
}, },
"js-tokens": { "js-tokens": {
"version": "4.0.0", "version": "4.0.0",
@ -1548,9 +1548,9 @@
} }
}, },
"lib0": { "lib0": {
"version": "0.2.29", "version": "0.2.32",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.29.tgz", "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.32.tgz",
"integrity": "sha512-bcQqmh3bUDVXwZrAJnekTpNk0uaShRg1bHMK7uzBIDFAWWdMXXCUtQXO/d/XsIxCiskgHTqF4jiQmDdoPCMVIw==", "integrity": "sha512-cHHKhHTojtvFSsthTk+CKuD17jMHIxuZxYpTzXj9TeQLPNoGNDPl6ax+J6eFETVe3ZvPMh3V0nGfJgGo6QgSvA==",
"requires": { "requires": {
"isomorphic.js": "^0.1.3" "isomorphic.js": "^0.1.3"
} }
@ -1716,18 +1716,18 @@
"dev": true "dev": true
}, },
"markdownlint": { "markdownlint": {
"version": "0.20.3", "version": "0.20.4",
"resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.20.3.tgz", "resolved": "https://registry.npmjs.org/markdownlint/-/markdownlint-0.20.4.tgz",
"integrity": "sha512-J93s59tGvSFvAPWVUtEgxqPI0CHayTx1Z8poj1/4UJAquHGPIruWRMurkRldiNbgBiaQ4OOt15rHZbFfU6u05A==", "integrity": "sha512-jpfaPgjT0OpeBbemjYNZbzGG3hCLcAIvrm/pEY3+q/szDScG6ZonDacqySVRJAv9glbo8y4wBPJ0wgW17+9GGA==",
"dev": true, "dev": true,
"requires": { "requires": {
"markdown-it": "10.0.0" "markdown-it": "10.0.0"
} }
}, },
"markdownlint-cli": { "markdownlint-cli": {
"version": "0.23.1", "version": "0.23.2",
"resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.23.1.tgz", "resolved": "https://registry.npmjs.org/markdownlint-cli/-/markdownlint-cli-0.23.2.tgz",
"integrity": "sha512-UARWuPILksAcVLTosUv1F1tLognNYQ/qjLRIgWwQAYqdl3QQrTPurU/X9Z2jrdAJYlOim868QsufxjYJpH0K7Q==", "integrity": "sha512-OSl5OZ8xzGN6z355cqRkiq67zPi3reJimklaF72p0554q85Dng5ToOjjSB9tDKZebSt85jX8cp+ruoQlPqOsPA==",
"dev": true, "dev": true,
"requires": { "requires": {
"commander": "~2.9.0", "commander": "~2.9.0",
@ -1739,8 +1739,8 @@
"jsonc-parser": "~2.2.0", "jsonc-parser": "~2.2.0",
"lodash.differencewith": "~4.5.0", "lodash.differencewith": "~4.5.0",
"lodash.flatten": "~4.4.0", "lodash.flatten": "~4.4.0",
"markdownlint": "~0.20.3", "markdownlint": "~0.20.4",
"markdownlint-rule-helpers": "~0.10.0", "markdownlint-rule-helpers": "~0.11.0",
"minimatch": "~3.0.4", "minimatch": "~3.0.4",
"minimist": "~1.2.5", "minimist": "~1.2.5",
"rc": "~1.2.7" "rc": "~1.2.7"
@ -1770,9 +1770,9 @@
} }
}, },
"markdownlint-rule-helpers": { "markdownlint-rule-helpers": {
"version": "0.10.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.10.0.tgz", "resolved": "https://registry.npmjs.org/markdownlint-rule-helpers/-/markdownlint-rule-helpers-0.11.0.tgz",
"integrity": "sha512-0e8VUTjNdQwS7hTyNan9oOLsy4a7KEsXo3fxKMDRFRk6Jn+pLB3iKZ3mj/m6ECrlOUCxPYYmgOmmyk3bSdbIvw==", "integrity": "sha512-PhGii9dOiDJDXxiRMpK8N0FM9powprvRPsXALgkjlSPTwLh6ymH+iF3iUe3nq8KGu26tclFBlLL5xAGy/zb7FA==",
"dev": true "dev": true
}, },
"marked": { "marked": {
@ -2786,9 +2786,9 @@
"dev": true "dev": true
}, },
"typescript": { "typescript": {
"version": "3.9.3", "version": "3.9.6",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.3.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.6.tgz",
"integrity": "sha512-D/wqnB2xzNFIcoBG9FG8cXRDjiqSTbG2wd8DMZeQyJlP1vfTkIxH4GKveWaEBYySKIg+USu+E+EDIR47SqnaMQ==", "integrity": "sha512-Pspx3oKAPJtjNwE92YS05HQoY7z2SFyOpHo9MqJor3BXAGNaPUs83CuVp9VISFkSjyRfiTpmKuAYGJB7S7hOxw==",
"dev": true "dev": true
}, },
"uc.micro": { "uc.micro": {

View File

@ -18,7 +18,7 @@
"lint": "markdownlint README.md && standard && tsc", "lint": "markdownlint README.md && standard && tsc",
"docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.md --package ./package.json || true", "docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.md --package ./package.json || true",
"serve-docs": "npm run docs && http-server ./docs/", "serve-docs": "npm run docs && http-server ./docs/",
"preversion": "npm run lint && PRODUCTION=1 npm run dist && npm run docs && node ./dist/tests.cjs --repitition-time 1000", "preversion": "npm run lint && PRODUCTION=1 npm run dist && npm run docs && node ./dist/tests.cjs --repitition-time 1000 && test -e dist/src/index.d.ts && test -e dist/yjs.cjs && test -e dist/yjs.cjs",
"debug": "concurrently 'http-server -o test.html' 'npm run watch'", "debug": "concurrently 'http-server -o test.html' 'npm run watch'",
"trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.cjs", "trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.cjs",
"trace-opt": "clear && rollup -c && node --trace-opt dist/test.cjs" "trace-opt": "clear && rollup -c && node --trace-opt dist/test.cjs"
@ -60,7 +60,7 @@
}, },
"homepage": "https://yjs.dev", "homepage": "https://yjs.dev",
"dependencies": { "dependencies": {
"lib0": "^0.2.29" "lib0": "^0.2.32"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^11.1.0", "@rollup/plugin-commonjs": "^11.1.0",
@ -68,12 +68,12 @@
"concurrently": "^3.6.1", "concurrently": "^3.6.1",
"http-server": "^0.12.3", "http-server": "^0.12.3",
"jsdoc": "^3.6.4", "jsdoc": "^3.6.4",
"markdownlint-cli": "^0.23.1", "markdownlint-cli": "^0.23.2",
"rollup": "^1.32.1", "rollup": "^1.32.1",
"rollup-cli": "^1.0.9", "rollup-cli": "^1.0.9",
"standard": "^14.3.4", "standard": "^14.3.4",
"tui-jsdoc-template": "^1.2.2", "tui-jsdoc-template": "^1.2.2",
"typescript": "^3.9.3", "typescript": "^3.9.6",
"y-protocols": "^0.2.3" "y-protocols": "^0.2.3"
} }
} }

View File

@ -48,12 +48,18 @@ export {
typeMapGetSnapshot, typeMapGetSnapshot,
iterateDeletedStructs, iterateDeletedStructs,
applyUpdate, applyUpdate,
applyUpdateV2,
readUpdate, readUpdate,
readUpdateV2,
encodeStateAsUpdate, encodeStateAsUpdate,
encodeStateAsUpdateV2,
encodeStateVector, encodeStateVector,
encodeStateVectorV2,
UndoManager, UndoManager,
decodeSnapshot, decodeSnapshot,
encodeSnapshot, encodeSnapshot,
decodeSnapshotV2,
encodeSnapshotV2,
isDeleted, isDeleted,
isParentOf, isParentOf,
equalSnapshots, equalSnapshots,

View File

@ -1,6 +1,8 @@
export * from './utils/DeleteSet.js' export * from './utils/DeleteSet.js'
export * from './utils/Doc.js' export * from './utils/Doc.js'
export * from './utils/UpdateDecoder.js'
export * from './utils/UpdateEncoder.js'
export * from './utils/encoding.js' export * from './utils/encoding.js'
export * from './utils/EventHandler.js' export * from './utils/EventHandler.js'
export * from './utils/ID.js' export * from './utils/ID.js'

View File

@ -1,9 +1,8 @@
import { import {
StructStore, ID, Transaction // eslint-disable-line AbstractUpdateEncoder, ID, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
import * as error from 'lib0/error.js' import * as error from 'lib0/error.js'
export class AbstractStruct { export class AbstractStruct {
@ -35,7 +34,7 @@ export class AbstractStruct {
} }
/** /**
* @param {encoding.Encoder} encoder The encoder to write data to. * @param {AbstractUpdateEncoder} encoder The encoder to write data to.
* @param {number} offset * @param {number} offset
* @param {number} encodingRef * @param {number} encodingRef
*/ */

View File

@ -1,10 +1,7 @@
import { import {
Transaction, Item, StructStore // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, Transaction, Item, StructStore // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
export class ContentAny { export class ContentAny {
/** /**
* @param {Array<any>} arr * @param {Array<any>} arr
@ -77,15 +74,15 @@ export class ContentAny {
*/ */
gc (store) {} gc (store) {}
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {number} offset * @param {number} offset
*/ */
write (encoder, offset) { write (encoder, offset) {
const len = this.arr.length const len = this.arr.length
encoding.writeVarUint(encoder, len - offset) encoder.writeLen(len - offset)
for (let i = offset; i < len; i++) { for (let i = offset; i < len; i++) {
const c = this.arr[i] const c = this.arr[i]
encoding.writeAny(encoder, c) encoder.writeAny(c)
} }
} }
@ -98,14 +95,14 @@ export class ContentAny {
} }
/** /**
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {ContentAny} * @return {ContentAny}
*/ */
export const readContentAny = decoder => { export const readContentAny = decoder => {
const len = decoding.readVarUint(decoder) const len = decoder.readLen()
const cs = [] const cs = []
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
cs.push(decoding.readAny(decoder)) cs.push(decoder.readAny())
} }
return new ContentAny(cs) return new ContentAny(cs)
} }

View File

@ -1,10 +1,7 @@
import { import {
StructStore, Item, Transaction // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, StructStore, Item, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import * as buffer from 'lib0/buffer.js'
import * as error from 'lib0/error.js' import * as error from 'lib0/error.js'
export class ContentBinary { export class ContentBinary {
@ -73,11 +70,11 @@ export class ContentBinary {
*/ */
gc (store) {} gc (store) {}
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {number} offset * @param {number} offset
*/ */
write (encoder, offset) { write (encoder, offset) {
encoding.writeVarUint8Array(encoder, this.content) encoder.writeBuf(this.content)
} }
/** /**
@ -89,7 +86,7 @@ export class ContentBinary {
} }
/** /**
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {ContentBinary} * @return {ContentBinary}
*/ */
export const readContentBinary = decoder => new ContentBinary(buffer.copyUint8Array(decoding.readVarUint8Array(decoder))) export const readContentBinary = decoder => new ContentBinary(decoder.readBuf())

View File

@ -1,12 +1,9 @@
import { import {
addToDeleteSet, addToDeleteSet,
StructStore, Item, Transaction // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, StructStore, Item, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
export class ContentDeleted { export class ContentDeleted {
/** /**
* @param {number} len * @param {number} len
@ -67,7 +64,7 @@ export class ContentDeleted {
* @param {Item} item * @param {Item} item
*/ */
integrate (transaction, item) { integrate (transaction, item) {
addToDeleteSet(transaction.deleteSet, item.id, this.len) addToDeleteSet(transaction.deleteSet, item.id.client, item.id.clock, this.len)
item.markDeleted() item.markDeleted()
} }
@ -80,11 +77,11 @@ export class ContentDeleted {
*/ */
gc (store) {} gc (store) {}
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {number} offset * @param {number} offset
*/ */
write (encoder, offset) { write (encoder, offset) {
encoding.writeVarUint(encoder, this.len - offset) encoder.writeLen(this.len - offset)
} }
/** /**
@ -98,7 +95,7 @@ export class ContentDeleted {
/** /**
* @private * @private
* *
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {ContentDeleted} * @return {ContentDeleted}
*/ */
export const readContentDeleted = decoder => new ContentDeleted(decoding.readVarUint(decoder)) export const readContentDeleted = decoder => new ContentDeleted(decoder.readLen())

View File

@ -1,10 +1,8 @@
import { import {
StructStore, Item, Transaction // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, StructStore, Item, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import * as error from 'lib0/error.js' import * as error from 'lib0/error.js'
/** /**
@ -76,11 +74,11 @@ export class ContentEmbed {
*/ */
gc (store) {} gc (store) {}
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {number} offset * @param {number} offset
*/ */
write (encoder, offset) { write (encoder, offset) {
encoding.writeVarString(encoder, JSON.stringify(this.embed)) encoder.writeJSON(this.embed)
} }
/** /**
@ -94,7 +92,7 @@ export class ContentEmbed {
/** /**
* @private * @private
* *
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {ContentEmbed} * @return {ContentEmbed}
*/ */
export const readContentEmbed = decoder => new ContentEmbed(JSON.parse(decoding.readVarString(decoder))) export const readContentEmbed = decoder => new ContentEmbed(decoder.readJSON())

View File

@ -1,10 +1,8 @@
import { import {
Item, StructStore, Transaction // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, Item, StructStore, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import * as error from 'lib0/error.js' import * as error from 'lib0/error.js'
/** /**
@ -78,12 +76,12 @@ export class ContentFormat {
*/ */
gc (store) {} gc (store) {}
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {number} offset * @param {number} offset
*/ */
write (encoder, offset) { write (encoder, offset) {
encoding.writeVarString(encoder, this.key) encoder.writeKey(this.key)
encoding.writeVarString(encoder, JSON.stringify(this.value)) encoder.writeJSON(this.value)
} }
/** /**
@ -95,7 +93,7 @@ export class ContentFormat {
} }
/** /**
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {ContentFormat} * @return {ContentFormat}
*/ */
export const readContentFormat = decoder => new ContentFormat(decoding.readVarString(decoder), JSON.parse(decoding.readVarString(decoder))) export const readContentFormat = decoder => new ContentFormat(decoder.readString(), decoder.readJSON())

View File

@ -1,10 +1,7 @@
import { import {
Transaction, Item, StructStore // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, Transaction, Item, StructStore // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
/** /**
* @private * @private
*/ */
@ -80,15 +77,15 @@ export class ContentJSON {
*/ */
gc (store) {} gc (store) {}
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {number} offset * @param {number} offset
*/ */
write (encoder, offset) { write (encoder, offset) {
const len = this.arr.length const len = this.arr.length
encoding.writeVarUint(encoder, len - offset) encoder.writeLen(len - offset)
for (let i = offset; i < len; i++) { for (let i = offset; i < len; i++) {
const c = this.arr[i] const c = this.arr[i]
encoding.writeVarString(encoder, c === undefined ? 'undefined' : JSON.stringify(c)) encoder.writeString(c === undefined ? 'undefined' : JSON.stringify(c))
} }
} }
@ -103,14 +100,14 @@ export class ContentJSON {
/** /**
* @private * @private
* *
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {ContentJSON} * @return {ContentJSON}
*/ */
export const readContentJSON = decoder => { export const readContentJSON = decoder => {
const len = decoding.readVarUint(decoder) const len = decoder.readLen()
const cs = [] const cs = []
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
const c = decoding.readVarString(decoder) const c = decoder.readString()
if (c === 'undefined') { if (c === 'undefined') {
cs.push(undefined) cs.push(undefined)
} else { } else {

View File

@ -1,10 +1,7 @@
import { import {
Transaction, Item, StructStore // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, Transaction, Item, StructStore // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
/** /**
* @private * @private
*/ */
@ -80,11 +77,11 @@ export class ContentString {
*/ */
gc (store) {} gc (store) {}
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {number} offset * @param {number} offset
*/ */
write (encoder, offset) { write (encoder, offset) {
encoding.writeVarString(encoder, offset === 0 ? this.str : this.str.slice(offset)) encoder.writeString(offset === 0 ? this.str : this.str.slice(offset))
} }
/** /**
@ -98,7 +95,7 @@ export class ContentString {
/** /**
* @private * @private
* *
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {ContentString} * @return {ContentString}
*/ */
export const readContentString = decoder => new ContentString(decoding.readVarString(decoder)) export const readContentString = decoder => new ContentString(decoder.readString())

View File

@ -7,15 +7,13 @@ import {
readYXmlFragment, readYXmlFragment,
readYXmlHook, readYXmlHook,
readYXmlText, readYXmlText,
ID, StructStore, Transaction, Item, YEvent, AbstractType // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, StructStore, Transaction, Item, YEvent, AbstractType // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
import * as decoding from 'lib0/decoding.js'
import * as error from 'lib0/error.js' import * as error from 'lib0/error.js'
/** /**
* @type {Array<function(decoding.Decoder):AbstractType<any>>} * @type {Array<function(AbstractUpdateDecoder):AbstractType<any>>}
* @private * @private
*/ */
export const typeRefs = [ export const typeRefs = [
@ -150,7 +148,7 @@ export class ContentType {
} }
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {number} offset * @param {number} offset
*/ */
write (encoder, offset) { write (encoder, offset) {
@ -168,7 +166,7 @@ export class ContentType {
/** /**
* @private * @private
* *
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {ContentType} * @return {ContentType}
*/ */
export const readContentType = decoder => new ContentType(typeRefs[decoding.readVarUint(decoder)](decoder)) export const readContentType = decoder => new ContentType(typeRefs[decoder.readTypeRef()](decoder))

View File

@ -2,11 +2,9 @@
import { import {
AbstractStruct, AbstractStruct,
addStruct, addStruct,
StructStore, Transaction, ID // eslint-disable-line AbstractUpdateEncoder, StructStore, Transaction, ID // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
export const structGCRefNumber = 0 export const structGCRefNumber = 0
/** /**
@ -41,12 +39,12 @@ export class GC extends AbstractStruct {
} }
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {number} offset * @param {number} offset
*/ */
write (encoder, offset) { write (encoder, offset) {
encoding.writeUint8(encoder, structGCRefNumber) encoder.writeInfo(structGCRefNumber)
encoding.writeVarUint(encoder, this.length - offset) encoder.writeLen(this.length - offset)
} }
/** /**

View File

@ -1,7 +1,5 @@
import { import {
readID,
writeID,
GC, GC,
getState, getState,
AbstractStruct, AbstractStruct,
@ -23,12 +21,10 @@ import {
readContentFormat, readContentFormat,
readContentType, readContentType,
addChangedTypeToTransaction, addChangedTypeToTransaction,
Doc, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as error from 'lib0/error.js' import * as error from 'lib0/error.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import * as maplib from 'lib0/map.js' import * as maplib from 'lib0/map.js'
import * as set from 'lib0/set.js' import * as set from 'lib0/set.js'
import * as binary from 'lib0/binary.js' import * as binary from 'lib0/binary.js'
@ -574,7 +570,7 @@ export class Item extends AbstractStruct {
parent._length -= this.length parent._length -= this.length
} }
this.markDeleted() this.markDeleted()
addToDeleteSet(transaction.deleteSet, this.id, this.length) addToDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length)
maplib.setIfUndefined(transaction.changed, parent, set.create).add(this.parentSub) maplib.setIfUndefined(transaction.changed, parent, set.create).add(this.parentSub)
this.content.delete(transaction) this.content.delete(transaction)
} }
@ -602,7 +598,7 @@ export class Item extends AbstractStruct {
* *
* This is called when this Item is sent to a remote peer. * This is called when this Item is sent to a remote peer.
* *
* @param {encoding.Encoder} encoder The encoder to write data to. * @param {AbstractUpdateEncoder} encoder The encoder to write data to.
* @param {number} offset * @param {number} offset
*/ */
write (encoder, offset) { write (encoder, offset) {
@ -613,12 +609,12 @@ export class Item extends AbstractStruct {
(origin === null ? 0 : binary.BIT8) | // origin is defined (origin === null ? 0 : binary.BIT8) | // origin is defined
(rightOrigin === null ? 0 : binary.BIT7) | // right origin is defined (rightOrigin === null ? 0 : binary.BIT7) | // right origin is defined
(parentSub === null ? 0 : binary.BIT6) // parentSub is non-null (parentSub === null ? 0 : binary.BIT6) // parentSub is non-null
encoding.writeUint8(encoder, info) encoder.writeInfo(info)
if (origin !== null) { if (origin !== null) {
writeID(encoder, origin) encoder.writeLeftID(origin)
} }
if (rightOrigin !== null) { if (rightOrigin !== null) {
writeID(encoder, rightOrigin) encoder.writeRightID(rightOrigin)
} }
if (origin === null && rightOrigin === null) { if (origin === null && rightOrigin === null) {
const parent = /** @type {AbstractType<any>} */ (this.parent) const parent = /** @type {AbstractType<any>} */ (this.parent)
@ -627,14 +623,14 @@ export class Item extends AbstractStruct {
// parent type on y._map // parent type on y._map
// find the correct key // find the correct key
const ykey = findRootTypeKey(parent) const ykey = findRootTypeKey(parent)
encoding.writeVarUint(encoder, 1) // write parentYKey encoder.writeParentInfo(true) // write parentYKey
encoding.writeVarString(encoder, ykey) encoder.writeString(ykey)
} else { } else {
encoding.writeVarUint(encoder, 0) // write parent id encoder.writeParentInfo(false) // write parent id
writeID(encoder, parentItem.id) encoder.writeLeftID(parentItem.id)
} }
if (parentSub !== null) { if (parentSub !== null) {
encoding.writeVarString(encoder, parentSub) encoder.writeString(parentSub)
} }
} }
this.content.write(encoder, offset) this.content.write(encoder, offset)
@ -642,15 +638,15 @@ export class Item extends AbstractStruct {
} }
/** /**
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @param {number} info * @param {number} info
*/ */
const readItemContent = (decoder, info) => contentRefs[info & binary.BITS5](decoder) export const readItemContent = (decoder, info) => contentRefs[info & binary.BITS5](decoder)
/** /**
* A lookup map for reading Item content. * A lookup map for reading Item content.
* *
* @type {Array<function(decoding.Decoder):AbstractContent>} * @type {Array<function(AbstractUpdateDecoder):AbstractContent>}
*/ */
export const contentRefs = [ export const contentRefs = [
() => { throw error.unexpectedCase() }, // GC is not ItemContent () => { throw error.unexpectedCase() }, // GC is not ItemContent
@ -741,7 +737,7 @@ export class AbstractContent {
} }
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {number} offset * @param {number} offset
*/ */
write (encoder, offset) { write (encoder, offset) {
@ -755,38 +751,3 @@ export class AbstractContent {
throw error.methodUnimplemented() throw error.methodUnimplemented()
} }
} }
/**
* @param {decoding.Decoder} decoder
* @param {ID} id
* @param {number} info
* @param {Doc} doc
*/
export const readItem = (decoder, id, info, doc) => {
/**
* The item that was originally to the left of this item.
* @type {ID | null}
*/
const origin = (info & binary.BIT8) === binary.BIT8 ? readID(decoder) : null
/**
* The item that was originally to the right of this item.
* @type {ID | null}
*/
const rightOrigin = (info & binary.BIT7) === binary.BIT7 ? readID(decoder) : null
const canCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0
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`
* and we read the next string as parentYKey.
* It indicates how we store/retrieve parent from `y.share`
* @type {string|null}
*/
const parentYKey = canCopyParentInfo && hasParentYKey ? decoding.readVarString(decoder) : null
return new Item(
id, null, origin, null, rightOrigin,
canCopyParentInfo && !hasParentYKey ? readID(decoder) : (parentYKey ? doc.get(parentYKey) : null), // parent
canCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoding.readVarString(decoder) : null, // parentSub
/** @type {AbstractContent} */ (readItemContent(decoder, info)) // item content
)
}

View File

@ -11,13 +11,12 @@ import {
ContentAny, ContentAny,
ContentBinary, ContentBinary,
getItemCleanStart, getItemCleanStart,
ID, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, // eslint-disable-line AbstractUpdateEncoder, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as map from 'lib0/map.js' import * as map from 'lib0/map.js'
import * as iterator from 'lib0/iterator.js' import * as iterator from 'lib0/iterator.js'
import * as error from 'lib0/error.js' import * as error from 'lib0/error.js'
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
/** /**
* Accumulate all (list) children of a type and return them as an Array. * Accumulate all (list) children of a type and return them as an Array.
@ -116,7 +115,7 @@ export class AbstractType {
} }
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
*/ */
_write (encoder) { } _write (encoder) { }

View File

@ -15,12 +15,9 @@ import {
YArrayRefID, YArrayRefID,
callTypeObservers, callTypeObservers,
transact, transact,
Doc, Transaction, Item // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, Doc, Transaction, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
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
* @template T * @template T
@ -204,15 +201,15 @@ export class YArray extends AbstractType {
} }
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
*/ */
_write (encoder) { _write (encoder) {
encoding.writeVarUint(encoder, YArrayRefID) encoder.writeTypeRef(YArrayRefID)
} }
} }
/** /**
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* *
* @private * @private
* @function * @function

View File

@ -14,11 +14,9 @@ import {
YMapRefID, YMapRefID,
callTypeObservers, callTypeObservers,
transact, transact,
Doc, Transaction, Item // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, Doc, Transaction, Item // 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 iterator from 'lib0/iterator.js' import * as iterator from 'lib0/iterator.js'
/** /**
@ -229,15 +227,15 @@ export class YMap extends AbstractType {
} }
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
*/ */
_write (encoder) { _write (encoder) {
encoding.writeVarUint(encoder, YMapRefID) encoder.writeTypeRef(YMapRefID)
} }
} }
/** /**
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* *
* @private * @private
* @function * @function

View File

@ -20,11 +20,9 @@ import {
splitSnapshotAffectedStructs, splitSnapshotAffectedStructs,
iterateDeletedStructs, iterateDeletedStructs,
iterateStructs, iterateStructs,
ID, Doc, Item, Snapshot, Transaction // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as decoding from 'lib0/decoding.js' // eslint-disable-line
import * as encoding from 'lib0/encoding.js'
import * as object from 'lib0/object.js' import * as object from 'lib0/object.js'
import * as map from 'lib0/map.js' import * as map from 'lib0/map.js'
@ -1096,15 +1094,15 @@ export class YText extends AbstractType {
} }
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
*/ */
_write (encoder) { _write (encoder) {
encoding.writeVarUint(encoder, YTextRefID) encoder.writeTypeRef(YTextRefID)
} }
} }
/** /**
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {YText} * @return {YText}
* *
* @private * @private

View File

@ -8,12 +8,9 @@ import {
typeMapGetAll, typeMapGetAll,
typeListForEach, typeListForEach,
YXmlElementRefID, YXmlElementRefID,
Snapshot, Doc, Item // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, Snapshot, Doc, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
/** /**
* An YXmlElement imitates the behavior of a * An YXmlElement imitates the behavior of a
* {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}. * {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element}.
@ -181,18 +178,18 @@ export class YXmlElement extends YXmlFragment {
* *
* This is called when this Item is sent to a remote peer. * This is called when this Item is sent to a remote peer.
* *
* @param {encoding.Encoder} encoder The encoder to write data to. * @param {AbstractUpdateEncoder} encoder The encoder to write data to.
*/ */
_write (encoder) { _write (encoder) {
encoding.writeVarUint(encoder, YXmlElementRefID) encoder.writeTypeRef(YXmlElementRefID)
encoding.writeVarString(encoder, this.nodeName) encoder.writeKey(this.nodeName)
} }
} }
/** /**
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {YXmlElement} * @return {YXmlElement}
* *
* @function * @function
*/ */
export const readYXmlElement = decoder => new YXmlElement(decoding.readVarString(decoder)) export const readYXmlElement = decoder => new YXmlElement(decoder.readKey())

View File

@ -14,12 +14,9 @@ import {
YXmlFragmentRefID, YXmlFragmentRefID,
callTypeObservers, callTypeObservers,
transact, transact,
Doc, ContentType, Transaction, Item, YXmlText, YXmlHook, Snapshot // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder, Doc, ContentType, Transaction, Item, YXmlText, YXmlHook, Snapshot // 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
/** /**
* Define the elements to which a set of CSS queries apply. * Define the elements to which a set of CSS queries apply.
* {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors} * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors}
@ -325,15 +322,15 @@ export class YXmlFragment extends AbstractType {
* *
* This is called when this Item is sent to a remote peer. * This is called when this Item is sent to a remote peer.
* *
* @param {encoding.Encoder} encoder The encoder to write data to. * @param {AbstractUpdateEncoder} encoder The encoder to write data to.
*/ */
_write (encoder) { _write (encoder) {
encoding.writeVarUint(encoder, YXmlFragmentRefID) encoder.writeTypeRef(YXmlFragmentRefID)
} }
} }
/** /**
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {YXmlFragment} * @return {YXmlFragment}
* *
* @private * @private

View File

@ -1,10 +1,9 @@
import { import {
YMap, YMap,
YXmlHookRefID YXmlHookRefID,
AbstractUpdateDecoder, AbstractUpdateEncoder // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
/** /**
* You can manage binding to a custom type with YXmlHook. * You can manage binding to a custom type with YXmlHook.
@ -66,21 +65,20 @@ export class YXmlHook extends YMap {
* *
* This is called when this Item is sent to a remote peer. * This is called when this Item is sent to a remote peer.
* *
* @param {encoding.Encoder} encoder The encoder to write data to. * @param {AbstractUpdateEncoder} encoder The encoder to write data to.
*/ */
_write (encoder) { _write (encoder) {
super._write(encoder) encoder.writeTypeRef(YXmlHookRefID)
encoding.writeVarUint(encoder, YXmlHookRefID) encoder.writeKey(this.hookName)
encoding.writeVarString(encoder, this.hookName)
} }
} }
/** /**
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {YXmlHook} * @return {YXmlHook}
* *
* @private * @private
* @function * @function
*/ */
export const readYXmlHook = decoder => export const readYXmlHook = decoder =>
new YXmlHook(decoding.readVarString(decoder)) new YXmlHook(decoder.readKey())

View File

@ -1,8 +1,9 @@
import { YText, YXmlTextRefID } from '../internals.js' import {
YText,
import * as encoding from 'lib0/encoding.js' YXmlTextRefID,
import * as decoding from 'lib0/decoding.js' // eslint-disable-line AbstractUpdateDecoder, AbstractUpdateEncoder // eslint-disable-line
} from '../internals.js'
/** /**
* Represents text in a Dom Element. In the future this type will also handle * Represents text in a Dom Element. In the future this type will also handle
@ -78,15 +79,15 @@ export class YXmlText extends YText {
} }
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
*/ */
_write (encoder) { _write (encoder) {
encoding.writeVarUint(encoder, YXmlTextRefID) encoder.writeTypeRef(YXmlTextRefID)
} }
} }
/** /**
* @param {decoding.Decoder} decoder * @param {AbstractUpdateDecoder} decoder
* @return {YXmlText} * @return {YXmlText}
* *
* @private * @private

View File

@ -3,9 +3,8 @@ import {
findIndexSS, findIndexSS,
getState, getState,
splitItem, splitItem,
createID,
iterateStructs, iterateStructs,
Item, AbstractStruct, GC, StructStore, Transaction, ID // eslint-disable-line AbstractUpdateDecoder, AbstractDSDecoder, AbstractDSEncoder, DSDecoderV2, DSEncoderV2, Item, GC, StructStore, Transaction, ID // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as array from 'lib0/array.js' import * as array from 'lib0/array.js'
@ -163,14 +162,15 @@ export const mergeDeleteSets = dss => {
/** /**
* @param {DeleteSet} ds * @param {DeleteSet} ds
* @param {ID} id * @param {number} client
* @param {number} clock
* @param {number} length * @param {number} length
* *
* @private * @private
* @function * @function
*/ */
export const addToDeleteSet = (ds, id, length) => { export const addToDeleteSet = (ds, client, clock, length) => {
map.setIfUndefined(ds.clients, id.client, () => []).push(new DeleteItem(id.clock, length)) map.setIfUndefined(ds.clients, client, () => []).push(new DeleteItem(clock, length))
} }
export const createDeleteSet = () => new DeleteSet() export const createDeleteSet = () => new DeleteSet()
@ -210,28 +210,29 @@ export const createDeleteSetFromStructStore = ss => {
} }
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractDSEncoder} encoder
* @param {DeleteSet} ds * @param {DeleteSet} ds
* *
* @private * @private
* @function * @function
*/ */
export const writeDeleteSet = (encoder, ds) => { export const writeDeleteSet = (encoder, ds) => {
encoding.writeVarUint(encoder, ds.clients.size) encoding.writeVarUint(encoder.restEncoder, ds.clients.size)
ds.clients.forEach((dsitems, client) => { ds.clients.forEach((dsitems, client) => {
encoding.writeVarUint(encoder, client) encoder.resetDsCurVal()
encoding.writeVarUint(encoder.restEncoder, client)
const len = dsitems.length const len = dsitems.length
encoding.writeVarUint(encoder, len) encoding.writeVarUint(encoder.restEncoder, len)
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
const item = dsitems[i] const item = dsitems[i]
encoding.writeVarUint(encoder, item.clock) encoder.writeDsClock(item.clock)
encoding.writeVarUint(encoder, item.len) encoder.writeDsLen(item.len)
} }
}) })
} }
/** /**
* @param {decoding.Decoder} decoder * @param {AbstractDSDecoder} decoder
* @return {DeleteSet} * @return {DeleteSet}
* *
* @private * @private
@ -239,19 +240,27 @@ export const writeDeleteSet = (encoder, ds) => {
*/ */
export const readDeleteSet = decoder => { export const readDeleteSet = decoder => {
const ds = new DeleteSet() const ds = new DeleteSet()
const numClients = decoding.readVarUint(decoder) const numClients = decoding.readVarUint(decoder.restDecoder)
for (let i = 0; i < numClients; i++) { for (let i = 0; i < numClients; i++) {
const client = decoding.readVarUint(decoder) decoder.resetDsCurVal()
const numberOfDeletes = decoding.readVarUint(decoder) const client = decoding.readVarUint(decoder.restDecoder)
const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
if (numberOfDeletes > 0) {
const dsField = map.setIfUndefined(ds.clients, client, () => [])
for (let i = 0; i < numberOfDeletes; i++) { for (let i = 0; i < numberOfDeletes; i++) {
addToDeleteSet(ds, createID(client, decoding.readVarUint(decoder)), decoding.readVarUint(decoder)) dsField.push(new DeleteItem(decoder.readDsClock(), decoder.readDsLen()))
}
} }
} }
return ds return ds
} }
/** /**
* @param {decoding.Decoder} decoder * @todo YDecoder also contains references to String and other Decoders. Would make sense to exchange YDecoder.toUint8Array for YDecoder.DsToUint8Array()..
*/
/**
* @param {AbstractDSDecoder} decoder
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {StructStore} store * @param {StructStore} store
* *
@ -260,18 +269,19 @@ export const readDeleteSet = decoder => {
*/ */
export const readAndApplyDeleteSet = (decoder, transaction, store) => { export const readAndApplyDeleteSet = (decoder, transaction, store) => {
const unappliedDS = new DeleteSet() const unappliedDS = new DeleteSet()
const numClients = decoding.readVarUint(decoder) const numClients = decoding.readVarUint(decoder.restDecoder)
for (let i = 0; i < numClients; i++) { for (let i = 0; i < numClients; i++) {
const client = decoding.readVarUint(decoder) decoder.resetDsCurVal()
const numberOfDeletes = decoding.readVarUint(decoder) const client = decoding.readVarUint(decoder.restDecoder)
const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
const structs = store.clients.get(client) || [] const structs = store.clients.get(client) || []
const state = getState(store, client) const state = getState(store, client)
for (let i = 0; i < numberOfDeletes; i++) { for (let i = 0; i < numberOfDeletes; i++) {
const clock = decoding.readVarUint(decoder) const clock = decoder.readDsClock()
const len = decoding.readVarUint(decoder) const clockEnd = clock + decoder.readDsLen()
if (clock < state) { if (clock < state) {
if (state < clock + len) { if (state < clockEnd) {
addToDeleteSet(unappliedDS, createID(client, state), clock + len - state) addToDeleteSet(unappliedDS, client, state, clockEnd - state)
} }
let index = findIndexSS(structs, clock) let index = findIndexSS(structs, clock)
/** /**
@ -288,10 +298,10 @@ export const readAndApplyDeleteSet = (decoder, transaction, store) => {
while (index < structs.length) { while (index < structs.length) {
// @ts-ignore // @ts-ignore
struct = structs[index++] struct = structs[index++]
if (struct.id.clock < clock + len) { if (struct.id.clock < clockEnd) {
if (!struct.deleted) { if (!struct.deleted) {
if (clock + len < struct.id.clock + struct.length) { if (clockEnd < struct.id.clock + struct.length) {
structs.splice(index, 0, splitItem(transaction, struct, clock + len - struct.id.clock)) structs.splice(index, 0, splitItem(transaction, struct, clockEnd - struct.id.clock))
} }
struct.delete(transaction) struct.delete(transaction)
} }
@ -300,14 +310,14 @@ export const readAndApplyDeleteSet = (decoder, transaction, store) => {
} }
} }
} else { } else {
addToDeleteSet(unappliedDS, createID(client, clock), len) addToDeleteSet(unappliedDS, client, clock, clockEnd - clock)
} }
} }
} }
if (unappliedDS.clients.size > 0) { if (unappliedDS.clients.size > 0) {
// TODO: no need for encoding+decoding ds anymore // TODO: no need for encoding+decoding ds anymore
const unappliedDSEncoder = encoding.createEncoder() const unappliedDSEncoder = new DSEncoderV2()
writeDeleteSet(unappliedDSEncoder, unappliedDS) writeDeleteSet(unappliedDSEncoder, unappliedDS)
store.pendingDeleteReaders.push(decoding.createDecoder(encoding.toUint8Array(unappliedDSEncoder))) store.pendingDeleteReaders.push(new DSDecoderV2(decoding.createDecoder((unappliedDSEncoder.toUint8Array()))))
} }
} }

View File

@ -5,11 +5,11 @@ import {
readDeleteSet, readDeleteSet,
writeDeleteSet, writeDeleteSet,
createDeleteSet, createDeleteSet,
ID, DeleteSet, YArrayEvent, Transaction, Doc // eslint-disable-line DSEncoderV1, DSDecoderV1, ID, DeleteSet, YArrayEvent, Transaction, Doc // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as decoding from 'lib0/decoding.js' import * as decoding from 'lib0/decoding.js'
import * as encoding from 'lib0/encoding.js'
import { mergeDeleteSets, isDeleted } from './DeleteSet.js' import { mergeDeleteSets, isDeleted } from './DeleteSet.js'
export class PermanentUserData { export class PermanentUserData {
@ -46,12 +46,12 @@ export class PermanentUserData {
event.changes.added.forEach(item => { event.changes.added.forEach(item => {
item.content.getContent().forEach(encodedDs => { item.content.getContent().forEach(encodedDs => {
if (encodedDs instanceof Uint8Array) { if (encodedDs instanceof Uint8Array) {
this.dss.set(userDescription, mergeDeleteSets([this.dss.get(userDescription) || createDeleteSet(), readDeleteSet(decoding.createDecoder(encodedDs))])) this.dss.set(userDescription, mergeDeleteSets([this.dss.get(userDescription) || createDeleteSet(), readDeleteSet(new DSDecoderV1(decoding.createDecoder(encodedDs)))]))
} }
}) })
}) })
}) })
this.dss.set(userDescription, mergeDeleteSets(ds.map(encodedDs => readDeleteSet(decoding.createDecoder(encodedDs))))) this.dss.set(userDescription, mergeDeleteSets(ds.map(encodedDs => readDeleteSet(new DSDecoderV1(encodedDs)))))
ids.observe(/** @param {YArrayEvent<any>} event */ event => ids.observe(/** @param {YArrayEvent<any>} event */ event =>
event.changes.added.forEach(item => item.content.getContent().forEach(addClientId)) event.changes.added.forEach(item => item.content.getContent().forEach(addClientId))
) )
@ -97,11 +97,11 @@ export class PermanentUserData {
user.get('ids').push([clientid]) user.get('ids').push([clientid])
} }
}) })
const encoder = encoding.createEncoder() const encoder = new DSEncoderV1()
const ds = this.dss.get(userDescription) const ds = this.dss.get(userDescription)
if (ds) { if (ds) {
writeDeleteSet(encoder, ds) writeDeleteSet(encoder, ds)
user.get('ds').push([encoding.toUint8Array(encoder)]) user.get('ds').push([encoder.toUint8Array()])
} }
} }
}, 0) }, 0)
@ -111,9 +111,9 @@ export class PermanentUserData {
const yds = user.get('ds') const yds = user.get('ds')
const ds = transaction.deleteSet const ds = transaction.deleteSet
if (transaction.local && ds.clients.size > 0 && filter(transaction, ds)) { if (transaction.local && ds.clients.size > 0 && filter(transaction, ds)) {
const encoder = encoding.createEncoder() const encoder = new DSEncoderV1()
writeDeleteSet(encoder, ds) writeDeleteSet(encoder, ds)
yds.push([encoding.toUint8Array(encoder)]) yds.push([encoder.toUint8Array()])
} }
}) })
}) })

View File

@ -12,13 +12,13 @@ import {
createDeleteSet, createDeleteSet,
createID, createID,
getState, getState,
Transaction, Doc, DeleteSet, Item // eslint-disable-line AbstractDSDecoder, AbstractDSEncoder, DSEncoderV1, DSEncoderV2, DSDecoderV1, DSDecoderV2, Transaction, Doc, DeleteSet, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as map from 'lib0/map.js' import * as map from 'lib0/map.js'
import * as set from 'lib0/set.js' import * as set from 'lib0/set.js'
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js' import * as decoding from 'lib0/decoding.js'
import { DefaultDSEncoder } from './encoding.js'
export class Snapshot { export class Snapshot {
/** /**
@ -74,23 +74,35 @@ export const equalSnapshots = (snap1, snap2) => {
/** /**
* @param {Snapshot} snapshot * @param {Snapshot} snapshot
* @param {AbstractDSEncoder} [encoder]
* @return {Uint8Array} * @return {Uint8Array}
*/ */
export const encodeSnapshot = snapshot => { export const encodeSnapshotV2 = (snapshot, encoder = new DSEncoderV2()) => {
const encoder = encoding.createEncoder()
writeDeleteSet(encoder, snapshot.ds) writeDeleteSet(encoder, snapshot.ds)
writeStateVector(encoder, snapshot.sv) writeStateVector(encoder, snapshot.sv)
return encoding.toUint8Array(encoder) return encoder.toUint8Array()
}
/**
* @param {Snapshot} snapshot
* @return {Uint8Array}
*/
export const encodeSnapshot = snapshot => encodeSnapshotV2(snapshot, new DefaultDSEncoder())
/**
* @param {Uint8Array} buf
* @param {AbstractDSDecoder} [decoder]
* @return {Snapshot}
*/
export const decodeSnapshotV2 = (buf, decoder = new DSDecoderV2(decoding.createDecoder(buf))) => {
return new Snapshot(readDeleteSet(decoder), readStateVector(decoder))
} }
/** /**
* @param {Uint8Array} buf * @param {Uint8Array} buf
* @return {Snapshot} * @return {Snapshot}
*/ */
export const decodeSnapshot = buf => { export const decodeSnapshot = buf => decodeSnapshotV2(buf, new DSDecoderV1(decoding.createDecoder(buf)))
const decoder = decoding.createDecoder(buf)
return new Snapshot(readDeleteSet(decoder), readStateVector(decoder))
}
/** /**
* @param {DeleteSet} ds * @param {DeleteSet} ds

View File

@ -2,12 +2,11 @@
import { import {
GC, GC,
splitItem, splitItem,
AbstractStruct, Transaction, ID, Item // eslint-disable-line Transaction, ID, Item, DSDecoderV2 // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as math from 'lib0/math.js' import * as math from 'lib0/math.js'
import * as error from 'lib0/error.js' import * as error from 'lib0/error.js'
import * as decoding from 'lib0/decoding.js' // eslint-disable-line
export class StructStore { export class StructStore {
constructor () { constructor () {
@ -31,7 +30,7 @@ export class StructStore {
*/ */
this.pendingStack = [] this.pendingStack = []
/** /**
* @type {Array<decoding.Decoder>} * @type {Array<DSDecoderV2>}
*/ */
this.pendingDeleteReaders = [] this.pendingDeleteReaders = []
} }

View File

@ -11,15 +11,16 @@ import {
Item, Item,
generateNewClientId, generateNewClientId,
createID, createID,
GC, StructStore, ID, AbstractType, AbstractStruct, YEvent, Doc // eslint-disable-line AbstractUpdateEncoder, GC, StructStore, UpdateEncoderV1, AbstractType, AbstractStruct, YEvent, Doc // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import * as encoding from 'lib0/encoding.js'
import * as map from 'lib0/map.js' import * as map from 'lib0/map.js'
import * as math from 'lib0/math.js' import * as math from 'lib0/math.js'
import * as set from 'lib0/set.js' import * as set from 'lib0/set.js'
import * as logging from 'lib0/logging.js' import * as logging from 'lib0/logging.js'
import { callAll } from 'lib0/function.js' import { callAll } from 'lib0/function.js'
import { DefaultUpdateEncoder } from './encoding.js'
import { UpdateEncoderV2 } from './UpdateEncoder.js'
/** /**
* A transaction is created for every change on the Yjs model. It is possible * A transaction is created for every change on the Yjs model. It is possible
@ -107,17 +108,18 @@ export class Transaction {
} }
/** /**
* @param {AbstractUpdateEncoder} encoder
* @param {Transaction} transaction * @param {Transaction} transaction
* @return {boolean} Whether data was written.
*/ */
export const computeUpdateMessageFromTransaction = transaction => { export const writeUpdateMessageFromTransaction = (encoder, transaction) => {
if (transaction.deleteSet.clients.size === 0 && !map.any(transaction.afterState, (clock, client) => transaction.beforeState.get(client) !== clock)) { if (transaction.deleteSet.clients.size === 0 && !map.any(transaction.afterState, (clock, client) => transaction.beforeState.get(client) !== clock)) {
return null return false
} }
const encoder = encoding.createEncoder()
sortAndMergeDeleteSet(transaction.deleteSet) sortAndMergeDeleteSet(transaction.deleteSet)
writeStructsFromTransaction(encoder, transaction) writeStructsFromTransaction(encoder, transaction)
writeDeleteSet(encoder, transaction.deleteSet) writeDeleteSet(encoder, transaction.deleteSet)
return encoder return true
} }
/** /**
@ -322,9 +324,17 @@ const cleanupTransactions = (transactionCleanups, i) => {
// @todo Merge all the transactions into one and provide send the data as a single update message // @todo Merge all the transactions into one and provide send the data as a single update message
doc.emit('afterTransactionCleanup', [transaction, doc]) doc.emit('afterTransactionCleanup', [transaction, doc])
if (doc._observers.has('update')) { if (doc._observers.has('update')) {
const updateMessage = computeUpdateMessageFromTransaction(transaction) const encoder = new DefaultUpdateEncoder()
if (updateMessage !== null) { const hasContent = writeUpdateMessageFromTransaction(encoder, transaction)
doc.emit('update', [encoding.toUint8Array(updateMessage), transaction.origin, doc]) if (hasContent) {
doc.emit('update', [encoder.toUint8Array(), transaction.origin, doc])
}
}
if (doc._observers.has('updateV2')) {
const encoder = new UpdateEncoderV2()
const hasContent = writeUpdateMessageFromTransaction(encoder, transaction)
if (hasContent) {
doc.emit('updateV2', [encoder.toUint8Array(), transaction.origin, doc])
} }
} }
if (transactionCleanups.length <= i + 1) { if (transactionCleanups.length <= i + 1) {

392
src/utils/UpdateDecoder.js Normal file
View File

@ -0,0 +1,392 @@
import * as buffer from 'lib0/buffer.js'
import * as error from 'lib0/error.js'
import * as decoding from 'lib0/decoding.js'
import {
ID, createID
} from '../internals.js'
export class AbstractDSDecoder {
/**
* @param {decoding.Decoder} decoder
*/
constructor (decoder) {
this.restDecoder = decoder
error.methodUnimplemented()
}
resetDsCurVal () { }
/**
* @return {number}
*/
readDsClock () {
error.methodUnimplemented()
}
/**
* @return {number}
*/
readDsLen () {
error.methodUnimplemented()
}
}
export class AbstractUpdateDecoder extends AbstractDSDecoder {
/**
* @return {ID}
*/
readLeftID () {
error.methodUnimplemented()
}
/**
* @return {ID}
*/
readRightID () {
error.methodUnimplemented()
}
/**
* Read the next client id.
* Use this in favor of readID whenever possible to reduce the number of objects created.
*
* @return {number}
*/
readClient () {
error.methodUnimplemented()
}
/**
* @return {number} info An unsigned 8-bit integer
*/
readInfo () {
error.methodUnimplemented()
}
/**
* @return {string}
*/
readString () {
error.methodUnimplemented()
}
/**
* @return {boolean} isKey
*/
readParentInfo () {
error.methodUnimplemented()
}
/**
* @return {number} info An unsigned 8-bit integer
*/
readTypeRef () {
error.methodUnimplemented()
}
/**
* Write len of a struct - well suited for Opt RLE encoder.
*
* @return {number} len
*/
readLen () {
error.methodUnimplemented()
}
/**
* @return {any}
*/
readAny () {
error.methodUnimplemented()
}
/**
* @return {Uint8Array}
*/
readBuf () {
error.methodUnimplemented()
}
/**
* Legacy implementation uses JSON parse. We use any-decoding in v2.
*
* @return {any}
*/
readJSON () {
error.methodUnimplemented()
}
/**
* @return {string}
*/
readKey () {
error.methodUnimplemented()
}
}
export class DSDecoderV1 {
/**
* @param {decoding.Decoder} decoder
*/
constructor (decoder) {
this.restDecoder = decoder
}
resetDsCurVal () {
// nop
}
/**
* @return {number}
*/
readDsClock () {
return decoding.readVarUint(this.restDecoder)
}
/**
* @return {number}
*/
readDsLen () {
return decoding.readVarUint(this.restDecoder)
}
}
export class UpdateDecoderV1 extends DSDecoderV1 {
/**
* @return {ID}
*/
readLeftID () {
return createID(decoding.readVarUint(this.restDecoder), decoding.readVarUint(this.restDecoder))
}
/**
* @return {ID}
*/
readRightID () {
return createID(decoding.readVarUint(this.restDecoder), decoding.readVarUint(this.restDecoder))
}
/**
* Read the next client id.
* Use this in favor of readID whenever possible to reduce the number of objects created.
*/
readClient () {
return decoding.readVarUint(this.restDecoder)
}
/**
* @return {number} info An unsigned 8-bit integer
*/
readInfo () {
return decoding.readUint8(this.restDecoder)
}
/**
* @return {string}
*/
readString () {
return decoding.readVarString(this.restDecoder)
}
/**
* @return {boolean} isKey
*/
readParentInfo () {
return decoding.readVarUint(this.restDecoder) === 1
}
/**
* @return {number} info An unsigned 8-bit integer
*/
readTypeRef () {
return decoding.readVarUint(this.restDecoder)
}
/**
* Write len of a struct - well suited for Opt RLE encoder.
*
* @return {number} len
*/
readLen () {
return decoding.readVarUint(this.restDecoder)
}
/**
* @return {any}
*/
readAny () {
return decoding.readAny(this.restDecoder)
}
/**
* @return {Uint8Array}
*/
readBuf () {
return buffer.copyUint8Array(decoding.readVarUint8Array(this.restDecoder))
}
/**
* Legacy implementation uses JSON parse. We use any-decoding in v2.
*
* @return {any}
*/
readJSON () {
return JSON.parse(decoding.readVarString(this.restDecoder))
}
/**
* @return {string}
*/
readKey () {
return decoding.readVarString(this.restDecoder)
}
}
export class DSDecoderV2 {
/**
* @param {decoding.Decoder} decoder
*/
constructor (decoder) {
this.dsCurrVal = 0
this.restDecoder = decoder
}
resetDsCurVal () {
this.dsCurrVal = 0
}
readDsClock () {
this.dsCurrVal += decoding.readVarUint(this.restDecoder)
return this.dsCurrVal
}
readDsLen () {
const diff = decoding.readVarUint(this.restDecoder) + 1
this.dsCurrVal += diff
return diff
}
}
export class UpdateDecoderV2 extends DSDecoderV2 {
/**
* @param {decoding.Decoder} decoder
*/
constructor (decoder) {
super(decoder)
/**
* List of cached keys. If the keys[id] does not exist, we read a new key
* from stringEncoder and push it to keys.
*
* @type {Array<string>}
*/
this.keys = []
decoding.readUint8(decoder) // read feature flag - currently unused
this.keyClockDecoder = new decoding.IntDiffOptRleDecoder(decoding.readVarUint8Array(decoder))
this.clientDecoder = new decoding.UintOptRleDecoder(decoding.readVarUint8Array(decoder))
this.leftClockDecoder = new decoding.IntDiffOptRleDecoder(decoding.readVarUint8Array(decoder))
this.rightClockDecoder = new decoding.IntDiffOptRleDecoder(decoding.readVarUint8Array(decoder))
this.infoDecoder = new decoding.RleDecoder(decoding.readVarUint8Array(decoder), decoding.readUint8)
this.stringDecoder = new decoding.StringDecoder(decoding.readVarUint8Array(decoder))
this.parentInfoDecoder = new decoding.RleDecoder(decoding.readVarUint8Array(decoder), decoding.readUint8)
this.typeRefDecoder = new decoding.UintOptRleDecoder(decoding.readVarUint8Array(decoder))
this.lenDecoder = new decoding.UintOptRleDecoder(decoding.readVarUint8Array(decoder))
}
/**
* @return {ID}
*/
readLeftID () {
return new ID(this.clientDecoder.read(), this.leftClockDecoder.read())
}
/**
* @return {ID}
*/
readRightID () {
return new ID(this.clientDecoder.read(), this.rightClockDecoder.read())
}
/**
* Read the next client id.
* Use this in favor of readID whenever possible to reduce the number of objects created.
*/
readClient () {
return this.clientDecoder.read()
}
/**
* @return {number} info An unsigned 8-bit integer
*/
readInfo () {
return /** @type {number} */ (this.infoDecoder.read())
}
/**
* @return {string}
*/
readString () {
return this.stringDecoder.read()
}
/**
* @return {boolean}
*/
readParentInfo () {
return this.parentInfoDecoder.read() === 1
}
/**
* @return {number} An unsigned 8-bit integer
*/
readTypeRef () {
return this.typeRefDecoder.read()
}
/**
* Write len of a struct - well suited for Opt RLE encoder.
*
* @return {number}
*/
readLen () {
return this.lenDecoder.read()
}
/**
* @return {any}
*/
readAny () {
return decoding.readAny(this.restDecoder)
}
/**
* @return {Uint8Array}
*/
readBuf () {
return decoding.readVarUint8Array(this.restDecoder)
}
/**
* This is mainly here for legacy purposes.
*
* Initial we incoded objects using JSON. Now we use the much faster lib0/any-encoder. This method mainly exists for legacy purposes for the v1 encoder.
*
* @return {any}
*/
readJSON () {
return decoding.readAny(this.restDecoder)
}
/**
* @return {string}
*/
readKey () {
const keyClock = this.keyClockDecoder.read()
if (keyClock < this.keys.length) {
return this.keys[keyClock]
} else {
const key = this.stringDecoder.read()
this.keys.push(key)
return key
}
}
}

408
src/utils/UpdateEncoder.js Normal file
View File

@ -0,0 +1,408 @@
import * as error from 'lib0/error.js'
import * as encoding from 'lib0/encoding.js'
import {
ID // eslint-disable-line
} from '../internals.js'
export class AbstractDSEncoder {
constructor () {
this.restEncoder = encoding.createEncoder()
}
/**
* @return {Uint8Array}
*/
toUint8Array () {
error.methodUnimplemented()
}
/**
* Resets the ds value to 0.
* The v2 encoder uses this information to reset the initial diff value.
*/
resetDsCurVal () { }
/**
* @param {number} clock
*/
writeDsClock (clock) { }
/**
* @param {number} len
*/
writeDsLen (len) { }
}
export class AbstractUpdateEncoder extends AbstractDSEncoder {
/**
* @return {Uint8Array}
*/
toUint8Array () {
error.methodUnimplemented()
}
/**
* @param {ID} id
*/
writeLeftID (id) { }
/**
* @param {ID} id
*/
writeRightID (id) { }
/**
* Use writeClient and writeClock instead of writeID if possible.
* @param {number} client
*/
writeClient (client) { }
/**
* @param {number} info An unsigned 8-bit integer
*/
writeInfo (info) { }
/**
* @param {string} s
*/
writeString (s) { }
/**
* @param {boolean} isYKey
*/
writeParentInfo (isYKey) { }
/**
* @param {number} info An unsigned 8-bit integer
*/
writeTypeRef (info) { }
/**
* Write len of a struct - well suited for Opt RLE encoder.
*
* @param {number} len
*/
writeLen (len) { }
/**
* @param {any} any
*/
writeAny (any) { }
/**
* @param {Uint8Array} buf
*/
writeBuf (buf) { }
/**
* @param {any} embed
*/
writeJSON (embed) { }
/**
* @param {string} key
*/
writeKey (key) { }
}
export class DSEncoderV1 {
constructor () {
this.restEncoder = new encoding.Encoder()
}
toUint8Array () {
return encoding.toUint8Array(this.restEncoder)
}
resetDsCurVal () {
// nop
}
/**
* @param {number} clock
*/
writeDsClock (clock) {
encoding.writeVarUint(this.restEncoder, clock)
}
/**
* @param {number} len
*/
writeDsLen (len) {
encoding.writeVarUint(this.restEncoder, len)
}
}
export class UpdateEncoderV1 extends DSEncoderV1 {
/**
* @param {ID} id
*/
writeLeftID (id) {
encoding.writeVarUint(this.restEncoder, id.client)
encoding.writeVarUint(this.restEncoder, id.clock)
}
/**
* @param {ID} id
*/
writeRightID (id) {
encoding.writeVarUint(this.restEncoder, id.client)
encoding.writeVarUint(this.restEncoder, id.clock)
}
/**
* Use writeClient and writeClock instead of writeID if possible.
* @param {number} client
*/
writeClient (client) {
encoding.writeVarUint(this.restEncoder, client)
}
/**
* @param {number} info An unsigned 8-bit integer
*/
writeInfo (info) {
encoding.writeUint8(this.restEncoder, info)
}
/**
* @param {string} s
*/
writeString (s) {
encoding.writeVarString(this.restEncoder, s)
}
/**
* @param {boolean} isYKey
*/
writeParentInfo (isYKey) {
encoding.writeVarUint(this.restEncoder, isYKey ? 1 : 0)
}
/**
* @param {number} info An unsigned 8-bit integer
*/
writeTypeRef (info) {
encoding.writeVarUint(this.restEncoder, info)
}
/**
* Write len of a struct - well suited for Opt RLE encoder.
*
* @param {number} len
*/
writeLen (len) {
encoding.writeVarUint(this.restEncoder, len)
}
/**
* @param {any} any
*/
writeAny (any) {
encoding.writeAny(this.restEncoder, any)
}
/**
* @param {Uint8Array} buf
*/
writeBuf (buf) {
encoding.writeVarUint8Array(this.restEncoder, buf)
}
/**
* @param {any} embed
*/
writeJSON (embed) {
encoding.writeVarString(this.restEncoder, JSON.stringify(embed))
}
/**
* @param {string} key
*/
writeKey (key) {
encoding.writeVarString(this.restEncoder, key)
}
}
export class DSEncoderV2 {
constructor () {
this.restEncoder = new encoding.Encoder() // encodes all the rest / non-optimized
this.dsCurrVal = 0
}
toUint8Array () {
return encoding.toUint8Array(this.restEncoder)
}
resetDsCurVal () {
this.dsCurrVal = 0
}
/**
* @param {number} clock
*/
writeDsClock (clock) {
const diff = clock - this.dsCurrVal
this.dsCurrVal = clock
encoding.writeVarUint(this.restEncoder, diff)
}
/**
* @param {number} len
*/
writeDsLen (len) {
if (len === 0) {
error.unexpectedCase()
}
encoding.writeVarUint(this.restEncoder, len - 1)
this.dsCurrVal += len
}
}
export class UpdateEncoderV2 extends DSEncoderV2 {
constructor () {
super()
/**
* @type {Map<string,number>}
*/
this.keyMap = new Map()
/**
* Refers to the next uniqe key-identifier to me used.
* See writeKey method for more information.
*
* @type {number}
*/
this.keyClock = 0
this.keyClockEncoder = new encoding.IntDiffOptRleEncoder()
this.clientEncoder = new encoding.UintOptRleEncoder()
this.leftClockEncoder = new encoding.IntDiffOptRleEncoder()
this.rightClockEncoder = new encoding.IntDiffOptRleEncoder()
this.infoEncoder = new encoding.RleEncoder(encoding.writeUint8)
this.stringEncoder = new encoding.StringEncoder()
this.parentInfoEncoder = new encoding.RleEncoder(encoding.writeUint8)
this.typeRefEncoder = new encoding.UintOptRleEncoder()
this.lenEncoder = new encoding.UintOptRleEncoder()
}
toUint8Array () {
const encoder = encoding.createEncoder()
encoding.writeUint8(encoder, 0) // this is a feature flag that we might use in the future
encoding.writeVarUint8Array(encoder, this.keyClockEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, this.clientEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, this.leftClockEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, this.rightClockEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, encoding.toUint8Array(this.infoEncoder))
encoding.writeVarUint8Array(encoder, this.stringEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, encoding.toUint8Array(this.parentInfoEncoder))
encoding.writeVarUint8Array(encoder, this.typeRefEncoder.toUint8Array())
encoding.writeVarUint8Array(encoder, this.lenEncoder.toUint8Array())
// @note The rest encoder is appended! (note the missing var)
encoding.writeUint8Array(encoder, encoding.toUint8Array(this.restEncoder))
return encoding.toUint8Array(encoder)
}
/**
* @param {ID} id
*/
writeLeftID (id) {
this.clientEncoder.write(id.client)
this.leftClockEncoder.write(id.clock)
}
/**
* @param {ID} id
*/
writeRightID (id) {
this.clientEncoder.write(id.client)
this.rightClockEncoder.write(id.clock)
}
/**
* @param {number} client
*/
writeClient (client) {
this.clientEncoder.write(client)
}
/**
* @param {number} info An unsigned 8-bit integer
*/
writeInfo (info) {
this.infoEncoder.write(info)
}
/**
* @param {string} s
*/
writeString (s) {
this.stringEncoder.write(s)
}
/**
* @param {boolean} isYKey
*/
writeParentInfo (isYKey) {
this.parentInfoEncoder.write(isYKey ? 1 : 0)
}
/**
* @param {number} info An unsigned 8-bit integer
*/
writeTypeRef (info) {
this.typeRefEncoder.write(info)
}
/**
* Write len of a struct - well suited for Opt RLE encoder.
*
* @param {number} len
*/
writeLen (len) {
this.lenEncoder.write(len)
}
/**
* @param {any} any
*/
writeAny (any) {
encoding.writeAny(this.restEncoder, any)
}
/**
* @param {Uint8Array} buf
*/
writeBuf (buf) {
encoding.writeVarUint8Array(this.restEncoder, buf)
}
/**
* This is mainly here for legacy purposes.
*
* Initial we incoded objects using JSON. Now we use the much faster lib0/any-encoder. This method mainly exists for legacy purposes for the v1 encoder.
*
* @param {any} embed
*/
writeJSON (embed) {
encoding.writeAny(this.restEncoder, embed)
}
/**
* Property keys are often reused. For example, in y-prosemirror the key `bold` might
* occur very often. For a 3d application, the key `position` might occur very often.
*
* We cache these keys in a Map and refer to them via a unique number.
*
* @param {string} key
*/
writeKey (key) {
const clock = this.keyMap.get(key)
if (clock === undefined) {
this.keyClockEncoder.write(this.keyClock++)
this.stringEncoder.write(key)
} else {
this.keyClockEncoder.write(this.keyClock++)
}
}
}

View File

@ -1,7 +1,8 @@
/** /**
* @module encoding * @module encoding
* */
/*
* We use the first five bits in the info flag for determining the type of the struct. * We use the first five bits in the info flag for determining the type of the struct.
* *
* 0: GC * 0: GC
@ -16,8 +17,6 @@
import { import {
findIndexSS, findIndexSS,
writeID,
readID,
getState, getState,
createID, createID,
getStateVector, getStateVector,
@ -25,16 +24,36 @@ import {
writeDeleteSet, writeDeleteSet,
createDeleteSetFromStructStore, createDeleteSetFromStructStore,
transact, transact,
readItem, readItemContent,
Doc, Transaction, GC, Item, StructStore, ID // eslint-disable-line UpdateDecoderV1,
UpdateDecoderV2,
UpdateEncoderV1,
UpdateEncoderV2,
DSDecoderV2,
DSEncoderV2,
DSDecoderV1,
DSEncoderV1,
AbstractDSEncoder, AbstractDSDecoder, AbstractUpdateEncoder, AbstractUpdateDecoder, AbstractContent, Doc, Transaction, GC, Item, StructStore, ID // eslint-disable-line
} from '../internals.js' } 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'
import * as binary from 'lib0/binary.js' import * as binary from 'lib0/binary.js'
export let DefaultDSEncoder = DSEncoderV1
export let DefaultDSDecoder = DSDecoderV1
export let DefaultUpdateEncoder = UpdateEncoderV1
export let DefaultUpdateDecoder = UpdateDecoderV1
export const useV2Encoding = () => {
DefaultDSEncoder = DSEncoderV2
DefaultDSDecoder = DSDecoderV2
DefaultUpdateEncoder = UpdateEncoderV2
DefaultUpdateDecoder = UpdateDecoderV2
}
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {Array<GC|Item>} structs All structs by `client` * @param {Array<GC|Item>} structs All structs by `client`
* @param {number} client * @param {number} client
* @param {number} clock write structs starting with `ID(client,clock)` * @param {number} clock write structs starting with `ID(client,clock)`
@ -45,8 +64,9 @@ const writeStructs = (encoder, structs, client, clock) => {
// write first id // write first id
const startNewStructs = findIndexSS(structs, clock) const startNewStructs = findIndexSS(structs, clock)
// write # encoded structs // write # encoded structs
encoding.writeVarUint(encoder, structs.length - startNewStructs) encoding.writeVarUint(encoder.restEncoder, structs.length - startNewStructs)
writeID(encoder, createID(client, clock)) encoder.writeClient(client)
encoding.writeVarUint(encoder.restEncoder, clock)
const firstStruct = structs[startNewStructs] const firstStruct = structs[startNewStructs]
// write first struct with an offset // write first struct with an offset
firstStruct.write(encoder, clock - firstStruct.id.clock) firstStruct.write(encoder, clock - firstStruct.id.clock)
@ -56,7 +76,7 @@ const writeStructs = (encoder, structs, client, clock) => {
} }
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {StructStore} store * @param {StructStore} store
* @param {Map<number,number>} _sm * @param {Map<number,number>} _sm
* *
@ -78,7 +98,7 @@ export const writeClientsStructs = (encoder, store, _sm) => {
} }
}) })
// write # states that were updated // write # states that were updated
encoding.writeVarUint(encoder, sm.size) encoding.writeVarUint(encoder.restEncoder, sm.size)
// Write items with higher client ids first // Write items with higher client ids first
// This heavily improves the conflict algorithm. // This heavily improves the conflict algorithm.
Array.from(sm.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => { Array.from(sm.entries()).sort((a, b) => b[0] - a[0]).forEach(([client, clock]) => {
@ -88,7 +108,7 @@ export const writeClientsStructs = (encoder, store, _sm) => {
} }
/** /**
* @param {decoding.Decoder} decoder The decoder object to read data from. * @param {AbstractUpdateDecoder} decoder The decoder object to read data from.
* @param {Map<number,Array<GC|Item>>} clientRefs * @param {Map<number,Array<GC|Item>>} clientRefs
* @param {Doc} doc * @param {Doc} doc
* @return {Map<number,Array<GC|Item>>} * @return {Map<number,Array<GC|Item>>}
@ -97,21 +117,52 @@ export const writeClientsStructs = (encoder, store, _sm) => {
* @function * @function
*/ */
export const readClientsStructRefs = (decoder, clientRefs, doc) => { export const readClientsStructRefs = (decoder, clientRefs, doc) => {
const numOfStateUpdates = decoding.readVarUint(decoder) const numOfStateUpdates = decoding.readVarUint(decoder.restDecoder)
for (let i = 0; i < numOfStateUpdates; i++) { for (let i = 0; i < numOfStateUpdates; i++) {
const numberOfStructs = decoding.readVarUint(decoder) const numberOfStructs = decoding.readVarUint(decoder.restDecoder)
/** /**
* @type {Array<GC|Item>} * @type {Array<GC|Item>}
*/ */
const refs = [] const refs = []
let { client, clock } = readID(decoder) const client = decoder.readClient()
let info, struct let clock = decoding.readVarUint(decoder.restDecoder)
clientRefs.set(client, refs) clientRefs.set(client, refs)
for (let i = 0; i < numberOfStructs; i++) { for (let i = 0; i < numberOfStructs; i++) {
info = decoding.readUint8(decoder) const info = decoder.readInfo()
struct = (binary.BITS5 & info) === 0 ? new GC(createID(client, clock), decoding.readVarUint(decoder)) : readItem(decoder, createID(client, clock), info, doc) if ((binary.BITS5 & info) !== 0) {
/**
* The item that was originally to the left of this item.
* @type {ID | null}
*/
const origin = (info & binary.BIT8) === binary.BIT8 ? decoder.readLeftID() : null
/**
* The item that was originally to the right of this item.
* @type {ID | null}
*/
const rightOrigin = (info & binary.BIT7) === binary.BIT7 ? decoder.readRightID() : null
const canCopyParentInfo = (info & (binary.BIT7 | binary.BIT8)) === 0
const hasParentYKey = canCopyParentInfo ? decoder.readParentInfo() : false
/**
* 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.
* It indicates how we store/retrieve parent from `y.share`
* @type {string|null}
*/
const parentYKey = canCopyParentInfo && hasParentYKey ? decoder.readString() : null
const struct = new Item(
createID(client, clock), null, origin, null, rightOrigin,
canCopyParentInfo && !hasParentYKey ? decoder.readLeftID() : (parentYKey ? doc.get(parentYKey) : null), // parent
canCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoder.readString() : null, // parentSub
/** @type {AbstractContent} */ (readItemContent(decoder, info)) // item content
)
refs.push(struct) refs.push(struct)
clock += struct.length clock += struct.length
} else {
const len = decoder.readLen()
refs.push(new GC(createID(client, clock), len))
clock += len
}
} }
} }
return clientRefs return clientRefs
@ -222,7 +273,7 @@ export const tryResumePendingDeleteReaders = (transaction, store) => {
} }
/** /**
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {Transaction} transaction * @param {Transaction} transaction
* *
* @private * @private
@ -275,7 +326,7 @@ const cleanupPendingStructs = pendingClientsStructRefs => {
* *
* This is called when data is received from a remote peer. * This is called when data is received from a remote peer.
* *
* @param {decoding.Decoder} decoder The decoder object to read data from. * @param {AbstractUpdateDecoder} decoder The decoder object to read data from.
* @param {Transaction} transaction * @param {Transaction} transaction
* @param {StructStore} store * @param {StructStore} store
* *
@ -299,15 +350,46 @@ export const readStructs = (decoder, transaction, store) => {
* @param {decoding.Decoder} decoder * @param {decoding.Decoder} decoder
* @param {Doc} ydoc * @param {Doc} ydoc
* @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))` * @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
* @param {AbstractUpdateDecoder} [structDecoder]
* *
* @function * @function
*/ */
export const readUpdate = (decoder, ydoc, transactionOrigin) => export const readUpdateV2 = (decoder, ydoc, transactionOrigin, structDecoder = new UpdateDecoderV2(decoder)) =>
transact(ydoc, transaction => { transact(ydoc, transaction => {
readStructs(decoder, transaction, ydoc.store) readStructs(structDecoder, transaction, ydoc.store)
readAndApplyDeleteSet(decoder, transaction, ydoc.store) readAndApplyDeleteSet(structDecoder, transaction, ydoc.store)
}, transactionOrigin, false) }, transactionOrigin, false)
/**
* Read and apply a document update.
*
* This function has the same effect as `applyUpdate` but accepts an decoder.
*
* @param {decoding.Decoder} decoder
* @param {Doc} ydoc
* @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
*
* @function
*/
export const readUpdate = (decoder, ydoc, transactionOrigin) => readUpdateV2(decoder, ydoc, transactionOrigin, new DefaultUpdateDecoder(decoder))
/**
* Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
*
* This function has the same effect as `readUpdate` but accepts an Uint8Array instead of a Decoder.
*
* @param {Doc} ydoc
* @param {Uint8Array} update
* @param {any} [transactionOrigin] This will be stored on `transaction.origin` and `.on('update', (update, origin))`
* @param {typeof UpdateDecoderV1 | typeof UpdateDecoderV2} [YDecoder]
*
* @function
*/
export const applyUpdateV2 = (ydoc, update, transactionOrigin, YDecoder = UpdateDecoderV2) => {
const decoder = decoding.createDecoder(update)
readUpdateV2(decoder, ydoc, transactionOrigin, new YDecoder(decoder))
}
/** /**
* Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`. * Apply a document update created by, for example, `y.on('update', update => ..)` or `update = encodeStateAsUpdate()`.
* *
@ -319,14 +401,13 @@ export const readUpdate = (decoder, ydoc, transactionOrigin) =>
* *
* @function * @function
*/ */
export const applyUpdate = (ydoc, update, transactionOrigin) => export const applyUpdate = (ydoc, update, transactionOrigin) => applyUpdateV2(ydoc, update, transactionOrigin, DefaultUpdateDecoder)
readUpdate(decoding.createDecoder(update), ydoc, transactionOrigin)
/** /**
* Write all the document as a single update message. If you specify the state of the remote client (`targetStateVector`) it will * Write all the document as a single update message. If you specify the state of the remote client (`targetStateVector`) it will
* only write the operations that are missing. * only write the operations that are missing.
* *
* @param {encoding.Encoder} encoder * @param {AbstractUpdateEncoder} encoder
* @param {Doc} doc * @param {Doc} doc
* @param {Map<number,number>} [targetStateVector] The state of the target that receives the update. Leave empty to write all known structs * @param {Map<number,number>} [targetStateVector] The state of the target that receives the update. Leave empty to write all known structs
* *
@ -345,31 +426,45 @@ export const writeStateAsUpdate = (encoder, doc, targetStateVector = new Map())
* *
* @param {Doc} doc * @param {Doc} doc
* @param {Uint8Array} [encodedTargetStateVector] The state of the target that receives the update. Leave empty to write all known structs * @param {Uint8Array} [encodedTargetStateVector] The state of the target that receives the update. Leave empty to write all known structs
* @param {AbstractUpdateEncoder} [encoder]
* @return {Uint8Array} * @return {Uint8Array}
* *
* @function * @function
*/ */
export const encodeStateAsUpdate = (doc, encodedTargetStateVector) => { export const encodeStateAsUpdateV2 = (doc, encodedTargetStateVector, encoder = new UpdateEncoderV2()) => {
const encoder = encoding.createEncoder()
const targetStateVector = encodedTargetStateVector == null ? new Map() : decodeStateVector(encodedTargetStateVector) const targetStateVector = encodedTargetStateVector == null ? new Map() : decodeStateVector(encodedTargetStateVector)
writeStateAsUpdate(encoder, doc, targetStateVector) writeStateAsUpdate(encoder, doc, targetStateVector)
return encoding.toUint8Array(encoder) return encoder.toUint8Array()
} }
/**
* Write all the document as a single update message that can be applied on the remote document. If you specify the state of the remote client (`targetState`) it will
* only write the operations that are missing.
*
* Use `writeStateAsUpdate` instead if you are working with lib0/encoding.js#Encoder
*
* @param {Doc} doc
* @param {Uint8Array} [encodedTargetStateVector] The state of the target that receives the update. Leave empty to write all known structs
* @return {Uint8Array}
*
* @function
*/
export const encodeStateAsUpdate = (doc, encodedTargetStateVector) => encodeStateAsUpdateV2(doc, encodedTargetStateVector, new DefaultUpdateEncoder())
/** /**
* Read state vector from Decoder and return as Map * Read state vector from Decoder and return as Map
* *
* @param {decoding.Decoder} decoder * @param {AbstractDSDecoder} decoder
* @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client. * @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
* *
* @function * @function
*/ */
export const readStateVector = decoder => { export const readStateVector = decoder => {
const ss = new Map() const ss = new Map()
const ssLength = decoding.readVarUint(decoder) const ssLength = decoding.readVarUint(decoder.restDecoder)
for (let i = 0; i < ssLength; i++) { for (let i = 0; i < ssLength; i++) {
const client = decoding.readVarUint(decoder) const client = decoding.readVarUint(decoder.restDecoder)
const clock = decoding.readVarUint(decoder) const clock = decoding.readVarUint(decoder.restDecoder)
ss.set(client, clock) ss.set(client, clock)
} }
return ss return ss
@ -383,28 +478,34 @@ export const readStateVector = decoder => {
* *
* @function * @function
*/ */
export const decodeStateVector = decodedState => readStateVector(decoding.createDecoder(decodedState)) export const decodeStateVectorV2 = decodedState => readStateVector(new DSDecoderV2(decoding.createDecoder(decodedState)))
/** /**
* Write State Vector to `lib0/encoding.js#Encoder`. * Read decodedState and return State as Map.
* *
* @param {encoding.Encoder} encoder * @param {Uint8Array} decodedState
* @return {Map<number,number>} Maps `client` to the number next expected `clock` from that client.
*
* @function
*/
export const decodeStateVector = decodedState => readStateVector(new DefaultDSDecoder(decoding.createDecoder(decodedState)))
/**
* @param {AbstractDSEncoder} encoder
* @param {Map<number,number>} sv * @param {Map<number,number>} sv
* @function * @function
*/ */
export const writeStateVector = (encoder, sv) => { export const writeStateVector = (encoder, sv) => {
encoding.writeVarUint(encoder, sv.size) encoding.writeVarUint(encoder.restEncoder, sv.size)
sv.forEach((clock, client) => { sv.forEach((clock, client) => {
encoding.writeVarUint(encoder, client) encoding.writeVarUint(encoder.restEncoder, client) // @todo use a special client decoder that is based on mapping
encoding.writeVarUint(encoder, clock) encoding.writeVarUint(encoder.restEncoder, clock)
}) })
return encoder return encoder
} }
/** /**
* Write State Vector to `lib0/encoding.js#Encoder`. * @param {AbstractDSEncoder} encoder
*
* @param {encoding.Encoder} encoder
* @param {Doc} doc * @param {Doc} doc
* *
* @function * @function
@ -415,12 +516,22 @@ export const writeDocumentStateVector = (encoder, doc) => writeStateVector(encod
* Encode State as Uint8Array. * Encode State as Uint8Array.
* *
* @param {Doc} doc * @param {Doc} doc
* @param {AbstractDSEncoder} [encoder]
* @return {Uint8Array} * @return {Uint8Array}
* *
* @function * @function
*/ */
export const encodeStateVector = doc => { export const encodeStateVectorV2 = (doc, encoder = new DSEncoderV2()) => {
const encoder = encoding.createEncoder()
writeDocumentStateVector(encoder, doc) writeDocumentStateVector(encoder, doc)
return encoding.toUint8Array(encoder) return encoder.toUint8Array()
} }
/**
* Encode State as Uint8Array.
*
* @param {Doc} doc
* @return {Uint8Array}
*
* @function
*/
export const encodeStateVector = doc => encodeStateVectorV2(doc, new DefaultDSEncoder())

View File

@ -394,21 +394,21 @@ export const applyRandomTests = (tc, mods, iterations, initTestObject) => {
const result = init(tc, { users: 5 }, initTestObject) const result = init(tc, { users: 5 }, initTestObject)
const { testConnector, users } = result const { testConnector, users } = result
for (let i = 0; i < iterations; i++) { for (let i = 0; i < iterations; i++) {
if (prng.int31(gen, 0, 100) <= 2) { if (prng.int32(gen, 0, 100) <= 2) {
// 2% chance to disconnect/reconnect a random user // 2% chance to disconnect/reconnect a random user
if (prng.bool(gen)) { if (prng.bool(gen)) {
testConnector.disconnectRandom() testConnector.disconnectRandom()
} else { } else {
testConnector.reconnectRandom() testConnector.reconnectRandom()
} }
} else if (prng.int31(gen, 0, 100) <= 1) { } else if (prng.int32(gen, 0, 100) <= 1) {
// 1% chance to flush all // 1% chance to flush all
testConnector.flushAllMessages() testConnector.flushAllMessages()
} else if (prng.int31(gen, 0, 100) <= 50) { } else if (prng.int32(gen, 0, 100) <= 50) {
// 50% chance to flush a random message // 50% chance to flush a random message
testConnector.flushRandomMessage() testConnector.flushRandomMessage()
} }
const user = prng.int31(gen, 0, users.length - 1) const user = prng.int32(gen, 0, users.length - 1)
const test = prng.oneOf(gen, mods) const test = prng.oneOf(gen, mods)
test(users[user], gen, result.testObjects[user]) test(users[user], gen, result.testObjects[user])
} }

View File

@ -5,6 +5,18 @@ import * as t from 'lib0/testing.js'
import * as prng from 'lib0/prng.js' import * as prng from 'lib0/prng.js'
import * as math from 'lib0/math.js' import * as math from 'lib0/math.js'
/**
* @param {t.TestCase} tc
*/
export const testBasicUpdate = tc => {
const doc1 = new Y.Doc()
const doc2 = new Y.Doc()
doc1.getArray('array').insert(0, ['hi'])
const update = Y.encodeStateAsUpdate(doc1)
Y.applyUpdate(doc2, update)
t.compare(doc2.getArray('array').toArray(), ['hi'])
}
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */
@ -335,23 +347,23 @@ const arrayTransactions = [
const yarray = user.getArray('array') const yarray = user.getArray('array')
var uniqueNumber = getUniqueNumber() var uniqueNumber = getUniqueNumber()
var content = [] var content = []
var len = prng.int31(gen, 1, 4) var len = prng.int32(gen, 1, 4)
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
content.push(uniqueNumber) content.push(uniqueNumber)
} }
var pos = prng.int31(gen, 0, yarray.length) var pos = prng.int32(gen, 0, yarray.length)
yarray.insert(pos, content) yarray.insert(pos, content)
}, },
function insertTypeArray (user, gen) { function insertTypeArray (user, gen) {
const yarray = user.getArray('array') const yarray = user.getArray('array')
var pos = prng.int31(gen, 0, yarray.length) var pos = prng.int32(gen, 0, yarray.length)
yarray.insert(pos, [new Y.Array()]) yarray.insert(pos, [new Y.Array()])
var array2 = yarray.get(pos) var array2 = yarray.get(pos)
array2.insert(0, [1, 2, 3, 4]) array2.insert(0, [1, 2, 3, 4])
}, },
function insertTypeMap (user, gen) { function insertTypeMap (user, gen) {
const yarray = user.getArray('array') const yarray = user.getArray('array')
var pos = prng.int31(gen, 0, yarray.length) var pos = prng.int32(gen, 0, yarray.length)
yarray.insert(pos, [new Y.Map()]) yarray.insert(pos, [new Y.Map()])
var map = yarray.get(pos) var map = yarray.get(pos)
map.set('someprop', 42) map.set('someprop', 42)
@ -362,13 +374,13 @@ const arrayTransactions = [
const yarray = user.getArray('array') const yarray = user.getArray('array')
var length = yarray.length var length = yarray.length
if (length > 0) { if (length > 0) {
var somePos = prng.int31(gen, 0, length - 1) var somePos = prng.int32(gen, 0, length - 1)
var delLength = prng.int31(gen, 1, math.min(2, length - somePos)) var delLength = prng.int32(gen, 1, math.min(2, length - somePos))
if (prng.bool(gen)) { if (prng.bool(gen)) {
var type = yarray.get(somePos) var type = yarray.get(somePos)
if (type.length > 0) { if (type.length > 0) {
somePos = prng.int31(gen, 0, type.length - 1) somePos = prng.int32(gen, 0, type.length - 1)
delLength = prng.int31(gen, 0, math.min(2, type.length - somePos)) delLength = prng.int32(gen, 0, math.min(2, type.length - somePos))
type.delete(somePos, delLength) type.delete(somePos, delLength)
} }
} else { } else {

View File

@ -215,7 +215,7 @@ const tryGc = () => {
* @param {t.TestCase} tc * @param {t.TestCase} tc
*/ */
export const testLargeFragmentedDocument = tc => { export const testLargeFragmentedDocument = tc => {
const itemsToInsert = 2000000 const itemsToInsert = 1000000
let update = /** @type {any} */ (null) let update = /** @type {any} */ (null)
;(() => { ;(() => {
const doc1 = new Y.Doc() const doc1 = new Y.Doc()
@ -230,14 +230,15 @@ export const testLargeFragmentedDocument = tc => {
}) })
tryGc() tryGc()
t.measureTime('time to encode document', () => { t.measureTime('time to encode document', () => {
update = Y.encodeStateAsUpdate(doc1) update = Y.encodeStateAsUpdateV2(doc1)
}) })
t.describe('Document size:', update.byteLength)
})() })()
;(() => { ;(() => {
const doc2 = new Y.Doc() const doc2 = new Y.Doc()
tryGc() tryGc()
t.measureTime(`time to apply ${itemsToInsert} updates`, () => { t.measureTime(`time to apply ${itemsToInsert} updates`, () => {
Y.applyUpdate(doc2, update) Y.applyUpdateV2(doc2, update)
}) })
})() })()
} }