more work

This commit is contained in:
Bartosz Sypytkowski 2023-05-30 09:05:06 +02:00
parent 740598cef2
commit 608a309f2c
8 changed files with 231 additions and 38 deletions

View File

@ -1,30 +1,33 @@
import { decoding, encoding, error } from 'lib0'
import {
UpdateEncoderV1, UpdateEncoderV2, UpdateDecoderV1, UpdateDecoderV2, Transaction, Item, StructStore // eslint-disable-line
UpdateEncoderV1, UpdateEncoderV2, UpdateDecoderV1, UpdateDecoderV2, Transaction, Item, StructStore, // eslint-disable-line
WeakLink,
findRootTypeKey,
ID,
find,
ContentType
} from '../internals.js'
export class ContentLink {
/**
* @param {Item} item
* @param {WeakLink<any>|{parent:string|ID,item:string|ID}} link
*/
constructor (item) {
/**
* @type {Item}
*/
this.item = item
constructor (link) {
this.link = link
}
/**
* @return {number}
*/
getLength () {
return this.item.length
return 1
}
/**
* @return {Array<any>}
*/
getContent () {
throw new Error('not implemented')
return [this.link]
}
/**
@ -38,7 +41,7 @@ import {
* @return {ContentLink}
*/
copy () {
return new ContentLink(this.item)
return new ContentLink(this.link)
}
/**
@ -46,7 +49,7 @@ import {
* @return {ContentLink}
*/
splice (offset) {
throw new Error('not implemented')
throw error.methodUnimplemented()
}
/**
@ -54,19 +57,57 @@ import {
* @return {boolean}
*/
mergeWith (right) {
throw new Error('not implemented')
return false
}
/**
* @param {Transaction} transaction
* @param {Item} item
*/
integrate (transaction, item) {}
integrate (transaction, item) {
if (this.link.constructor !== WeakLink) {
let { parent, item } = /** @type {any} */ (this.link)
let key = null
if (parent.constructor === ID) {
const parentItem = find(transaction.doc.store, parent)
if (parentItem.constructor === Item) {
parent = /** @type {ContentType} */ (parentItem.content).type
} else {
parent = null
}
} else {
parent = transaction.doc.share.get(parent)
}
if (item.constructor === ID) {
item = find(transaction.doc.store, item)
} else {
key = item
item = parent._map.get(key)
}
this.link = new WeakLink(parent, item, key)
}
const link = /** @type {WeakLink<any>} */ (this.link)
if (link.item.constructor === Item) {
if (link.item.linkedBy === null) {
link.item.linkedBy = new Set()
}
link.item.linkedBy.add(link)
}
}
/**
* @param {Transaction} transaction
*/
delete (transaction) {}
delete (transaction) {
const link = /** @type {WeakLink<any>} */ (this.link)
if (link.item.constructor === Item) {
if (link.item.linkedBy !== null) {
link.item.linkedBy.delete(link)
}
}
}
/**
* @param {StructStore} store
@ -78,14 +119,34 @@ import {
* @param {number} offset
*/
write (encoder, offset) {
throw new Error('not implemented')
const link = /** @type {WeakLink<any>} */ (this.link)
let flags = 0
const parentItem = link.source._item
if (parentItem) {
flags |= 1
}
if (link.key) {
flags |= 2
}
encoding.writeVarUint(encoder.restEncoder, flags)
if (parentItem) {
encoder.writeLeftID(parentItem.id)
} else {
const ykey = findRootTypeKey(link.source)
encoder.writeString(ykey)
}
if (link.key !== null) {
encoder.writeString(link.key)
} else {
encoder.writeLeftID(link.item.id)
}
}
/**
* @return {number}
*/
getRef () {
return 10
return 11
}
}
@ -94,6 +155,19 @@ import {
* @return {ContentLink}
*/
export const readContentWeakLink = decoder => {
throw new Error('not implemented')
const flags = decoding.readVarUint(decoder.restDecoder)
let parent
let item
if ((flags & 1) !== 0) {
parent = decoder.readLeftID()
} else {
parent = decoder.readString()
}
if ((flags & 2) !== 0) {
item = decoder.readString()
} else {
item = decoder.readLeftID()
}
return new ContentLink({parent, item})
}

View File

@ -24,7 +24,9 @@ import {
readContentType,
addChangedTypeToTransaction,
isDeleted,
StackItem, DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction // eslint-disable-line
StackItem, DeleteSet, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ContentType, ContentDeleted, StructStore, ID, AbstractType, Transaction, // eslint-disable-line
WeakLink,
ContentLink
} from '../internals.js'
import * as error from 'lib0/error'
@ -296,6 +298,13 @@ export class Item extends AbstractStruct {
* @type {ID | null}
*/
this.redone = null
/**
* If this item was referenced by other weak links, here we keep the references
* to these weak refs.
*
* @type {Set<WeakLink<any>> | null}
*/
this.linkedBy = null
/**
* @type {AbstractContent}
*/
@ -511,6 +520,7 @@ export class Item extends AbstractStruct {
/** @type {AbstractType<any>} */ (this.parent)._map.set(this.parentSub, this)
if (this.left !== null) {
// this is the current attribute value of parent. delete right
this.linkedBy = this.left.linkedBy
this.left.delete(transaction)
}
}
@ -579,6 +589,8 @@ export class Item extends AbstractStruct {
this.deleted === right.deleted &&
this.redone === null &&
right.redone === null &&
this.linkedBy === null &&
right.linkedBy === null &&
this.content.constructor === right.content.constructor &&
this.content.mergeWith(right.content)
) {
@ -624,6 +636,7 @@ export class Item extends AbstractStruct {
addToDeleteSet(transaction.deleteSet, this.id.client, this.id.clock, this.length)
addChangedTypeToTransaction(transaction, parent, this.parentSub)
this.content.delete(transaction)
this.linkedBy = null
}
}
@ -720,8 +733,8 @@ export const contentRefs = [
readContentType, // 7
readContentAny, // 8
readContentDoc, // 9
readContentWeakLink, // 10
() => { error.unexpectedCase() } // 10 - Skip is not ItemContent
() => { error.unexpectedCase() }, // 10 - Skip is not ItemContent
readContentWeakLink // 11
]
/**

View File

@ -11,7 +11,7 @@ import {
ContentAny,
ContentBinary,
getItemCleanStart,
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, // eslint-disable-line
ContentDoc, YText, YArray, UpdateEncoderV1, UpdateEncoderV2, Doc, Snapshot, Transaction, EventHandler, YEvent, Item, WeakLink, ContentLink, // eslint-disable-line
} from '../internals.js'
import * as map from 'lib0/map'
@ -669,6 +669,10 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem,
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentDoc(/** @type {Doc} */ (c)))
left.integrate(transaction, 0)
break
case WeakLink:
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentLink(/** @type {WeakLink<any>} */ (c)))
left.integrate(transaction, 0)
break
default:
if (c instanceof AbstractType) {
left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentType(c))
@ -851,6 +855,9 @@ export const typeMapSet = (transaction, parent, key, value) => {
case Doc:
content = new ContentDoc(/** @type {Doc} */ (value))
break
case WeakLink:
content = new ContentLink(/** @type {WeakLink<any>} */ (value))
break;
default:
if (value instanceof AbstractType) {
content = new ContentType(value)

View File

@ -1,3 +1,7 @@
import { AbstractType, GC, Item, createID } from "yjs"
import { findMarker, typeMapGet } from "./AbstractType.js"
import { error } from "lib0"
import { Transaction, getItemCleanEnd, getItemCleanStart } from "src/internals.js"
/**
* @template T
@ -5,13 +9,82 @@
* Weak link to another value stored somewhere in the document.
*/
export class WeakLink {
/**
* Returns a reference to an underlying value existing somewhere on in the document.
*
* @return {T|undefined}
*/
deref() {
throw new Error('not implemented')
/**
* @param {AbstractType<any>} source
* @param {Item|GC} item
* @param {string|null} key
*/
constructor(source, item, key) {
this.source = source
this.item = item
this.key = key
}
/**
* Returns a reference to an underlying value existing somewhere on in the document.
*
* @return {T|undefined}
*/
deref() {
if (this.key) {
return /** @type {T|undefined} */ (typeMapGet(this.source, this.key))
} else {
if (this.item.constructor === Item) {
return this.item.content.getContent()[0]
} else {
return undefined
}
}
}
}
const lengthExceeded = error.create('Length exceeded!')
/**
* Returns a {WeakLink} to an YArray element at given index.
*
* @param {Transaction} transaction
* @param {AbstractType<any>} parent
* @param {number} index
* @return {WeakLink<any>}
*/
export const arrayWeakLink = (transaction, parent, index) => {
const marker = findMarker(parent, index)
let n = parent._start
if (marker !== null) {
n = marker.p
index -= marker.index
}
for (; n !== null; n = n.right) {
if (!n.deleted && n.countable) {
if (index < n.length) {
if (index > 0) {
n = getItemCleanStart(transaction, createID(n.id.clock, n.id.clock + index))
}
if (n.length > 1) {
n = getItemCleanEnd(transaction, transaction.doc.store, createID(n.id.clock, n.id.clock + 1))
}
return new WeakLink(parent, n, null)
}
index -= n.length
}
}
throw lengthExceeded
}
/**
* Returns a {WeakLink} to an YMap element at given key.
*
* @param {AbstractType<any>} parent
* @param {string} key
* @return {WeakLink<any>|undefined}
*/
export const mapWeakLink = (parent, key) => {
const item = parent._map.get(key)
if (item !== undefined) {
return new WeakLink(parent, item, key)
} else {
return undefined
}
}

View File

@ -17,7 +17,8 @@ import {
callTypeObservers,
transact,
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line
WeakLink
WeakLink,
arrayWeakLink
} from '../internals.js'
import { typeListSlice } from './AbstractType.js'
@ -209,7 +210,13 @@ export class YArray extends AbstractType {
* @return {WeakLink<T>}
*/
link(index) {
throw new Error('Method not implemented.')
if (this.doc !== null) {
return transact(this.doc, transaction => {
return arrayWeakLink(transaction, this, index)
})
} else {
throw new Error('todo')
}
}
/**

View File

@ -15,7 +15,8 @@ import {
callTypeObservers,
transact,
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line
WeakLink
WeakLink,
mapWeakLink
} from '../internals.js'
import * as iterator from 'lib0/iterator'
@ -241,7 +242,7 @@ export class YMap extends AbstractType {
* @return {WeakLink<MapType>|undefined}
*/
link(key) {
throw new Error('Method not implemented.')
return mapWeakLink(this, key)
}
/**

View File

@ -21,7 +21,8 @@ if (isBrowser) {
log.createVConsole(document.body)
}
runTests({
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions, weakLinks
//doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions,
weakLinks
}).then(success => {
/* istanbul ignore next */
if (isNode) {

View File

@ -5,18 +5,35 @@ import { init, compare } from './testHelper.js'
/**
* @param {t.TestCase} tc
*/
export const testBasic = tc => {
export const testBasicMap = tc => {
const doc = new Y.Doc()
const map = doc.getMap('map')
const nested = new Y.Map()
nested.set('a1', 'hello')
map.set('a', nested)
const link = nested.link('a')
const link = map.link('a')
map.set('b', link)
const nested2 = map.get('b')
t.compare(nested2.toJSON(), nested.toJSON())
const link2 = /** @type {Y.WeakLink<any>} */ (map.get('b'))
const expected = nested.toJSON()
const actual = link2.deref().toJSON()
t.compare(actual, expected)
}
/**
* @param {t.TestCase} tc
*/
export const testBasicArray = tc => {
const doc = new Y.Doc()
const array = doc.getArray('array')
array.insert(0, [1,2,3])
array.insert(3, [array.link(1)])
t.compare(array.get(0), 1)
t.compare(array.get(1), 2)
t.compare(array.get(2), 3)
t.compare(array.get(3).deref(), 2)
}
/**