From 93a65faf0a37e0b37ce005c2f47ebee55feb04b0 Mon Sep 17 00:00:00 2001 From: Bartosz Sypytkowski Date: Wed, 31 May 2023 08:45:17 +0200 Subject: [PATCH] WeakLink: generally working except events --- src/structs/ContentLink.js | 141 ++++++++++++++++++++++--------------- src/structs/Item.js | 9 ++- src/types/AbstractType.js | 4 +- src/types/WeakLink.js | 45 ++++++++---- tests/weakLinks.tests.js | 18 +++-- 5 files changed, 137 insertions(+), 80 deletions(-) diff --git a/src/structs/ContentLink.js b/src/structs/ContentLink.js index 5598f828..1bdba6c7 100644 --- a/src/structs/ContentLink.js +++ b/src/structs/ContentLink.js @@ -15,10 +15,12 @@ import { export class ContentLink { /** - * @param {WeakLink|{parent:string|ID,item:string|ID}} link + * @param {WeakLink|null} link + * @param {{type:ID|null,tname:string|null,item:ID|null,key:string|null}|null} raw */ - constructor (link) { + constructor (link, raw) { this.link = link + this.raw = raw } /** @@ -46,7 +48,7 @@ import { * @return {ContentLink} */ copy () { - return new ContentLink(this.link) + return new ContentLink(this.link, this.raw) } /** @@ -70,38 +72,40 @@ import { * @param {Item} 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 (this.raw !== null) { + const { type, tname, key, item } = this.raw + let parent = null + if (type !== null) { + const parentItem = find(transaction.doc.store, type) if (parentItem.constructor === Item) { parent = /** @type {ContentType} */ (parentItem.content).type } else { parent = null } } else { - parent = transaction.doc.share.get(parent) + parent = /** @type {AbstractType} */ (transaction.doc.share.get(/** @type {string} */ (tname))) } - if (item.constructor === ID) { - item = getItemCleanStart(transaction, item) - if (item.length > 1) { - item = getItemCleanEnd(transaction, transaction.doc.store, createID(item.id.client, item.id.clock + 1)) + let target = null + if (item !== null) { + target = getItemCleanStart(transaction, item) + if (target.length > 1) { + target = getItemCleanEnd(transaction, transaction.doc.store, createID(target.id.client, target.id.clock + 1)) } - } else { - key = item - item = parent._map.get(key) + } else if (parent !== null) { + target = parent._map.get(/** @type {string} */ (key)) || null } - this.link = new WeakLink(parent, item, key) + const source = (parent !== null && target !== null) ? {parent: parent, item: target, key: key} : null + this.link = new WeakLink(source) + this.raw = null } - const link = /** @type {WeakLink} */ (this.link) - if (link.item.constructor === Item) { - if (link.item.linkedBy === null) { - link.item.linkedBy = new Set() + const linked = /** @type {WeakLink} */ (this.link).linkedItem() + if (linked !== undefined && linked.constructor === Item) { + if (linked.linkedBy === null) { + linked.linkedBy = new Set() } - link.item.linkedBy.add(link) + linked.linkedBy.add(/** @type {WeakLink} */ (this.link)) } } @@ -109,11 +113,14 @@ import { * @param {Transaction} transaction */ delete (transaction) { - const link = /** @type {WeakLink} */ (this.link) - if (link.item.constructor === Item) { - if (link.item.linkedBy !== null) { - link.item.linkedBy.delete(link) + if (this.link !== null && this.link.source !== null) { + const item = this.link.source.item + if (item !== null && item.constructor === Item) { + if (item.linkedBy !== null) { + item.linkedBy.delete(this.link) + } } + this.link.source = null } } @@ -127,26 +134,48 @@ import { * @param {number} offset */ write (encoder, offset) { - const link = /** @type {WeakLink} */ (this.link) + let type = null + let tname = null + let item = null + let key = null let flags = 0 - const parentItem = link.source._item - if (parentItem) { + if (this.raw !== null) { + type = this.raw.type + tname = this.raw.tname + key = this.raw.key + item = this.raw.item + } else { + const source = /** @type {WeakLink} */ (this.link).source + if (source !== null) { + if (source.parent._item !== null) { + type = source.parent._item.id + } else { + tname = findRootTypeKey(source.parent) + } + if (source.item !== null) { + item = source.item.id + } else { + key = source.key + } + } + } + + if (type !== null) { flags |= 1 } - if (link.key) { + if (item !== null) { flags |= 2 } encoding.writeVarUint(encoder.restEncoder, flags) - if (parentItem) { - encoder.writeLeftID(parentItem.id) + if (type !== null) { + encoder.writeLeftID(type) } else { - const ykey = findRootTypeKey(link.source) - encoder.writeString(ykey) + encoder.writeString(/** @type {string} */ (tname)) } - if (link.key !== null) { - encoder.writeString(link.key) + if (item !== null) { + encoder.writeLeftID(item) } else { - encoder.writeLeftID(link.item.id) + encoder.writeString(/** @type {string} */ (key)) } } @@ -164,19 +193,21 @@ import { */ export const readContentWeakLink = decoder => { const flags = decoding.readVarUint(decoder.restDecoder) - let parent - let item + let type = null + let tname = null + let item = null + let key = null if ((flags & 1) !== 0) { - parent = decoder.readLeftID() + type = decoder.readLeftID() } else { - parent = decoder.readString() + tname = decoder.readString() } if ((flags & 2) !== 0) { - item = decoder.readString() - } else { item = decoder.readLeftID() + } else { + key = decoder.readString() } - return new ContentLink({parent, item}) + return new ContentLink(null, { type, tname, item, key }) } const lengthExceeded = error.create('Length exceeded!') @@ -191,23 +222,23 @@ const lengthExceeded = error.create('Length exceeded!') */ export const arrayWeakLink = (transaction, parent, index) => { const marker = findMarker(parent, index) - let n = parent._start + let item = parent._start if (marker !== null) { - n = marker.p + item = marker.p index -= marker.index } - for (; n !== null; n = n.right) { - if (!n.deleted && n.countable) { - if (index < n.length) { + for (; item !== null; item = item.right) { + if (!item.deleted && item.countable) { + if (index < item.length) { if (index > 0) { - n = getItemCleanStart(transaction, createID(n.id.client, n.id.clock + index)) + item = getItemCleanStart(transaction, createID(item.id.client, item.id.clock + index)) } - if (n.length > 1) { - n = getItemCleanEnd(transaction, transaction.doc.store, createID(n.id.client, n.id.clock + 1)) + if (item.length > 1) { + item = getItemCleanEnd(transaction, transaction.doc.store, createID(item.id.client, item.id.clock + 1)) } - return new WeakLink(parent, n, null) + return new WeakLink({parent, item, key: null}) } - index -= n.length + index -= item.length } } @@ -224,7 +255,7 @@ export const arrayWeakLink = (transaction, parent, index) => { export const mapWeakLink = (parent, key) => { const item = parent._map.get(key) if (item !== undefined) { - return new WeakLink(parent, item, key) + return new WeakLink({parent, item, key}) } else { return undefined } diff --git a/src/structs/Item.js b/src/structs/Item.js index 3c510904..ce06345d 100644 --- a/src/structs/Item.js +++ b/src/structs/Item.js @@ -388,9 +388,12 @@ export class Item extends AbstractStruct { } if (this.content.constructor === ContentLink) { const content = /** @type {ContentLink} */ (this.content) - if (content.link.item.constructor === ID) { - if (content.link.item.client !== this.id.client) { - return content.link.item.client + if (content.raw !== null) { + const { type, item } = content.raw + if (type !== null && type.client !== this.id.client) { + return type.client + } else if (item !== null && item.client !== this.id.client) { + return item.client } } } diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js index 4f9830c8..9aaa95c9 100644 --- a/src/types/AbstractType.js +++ b/src/types/AbstractType.js @@ -670,7 +670,7 @@ export const typeListInsertGenericsAfter = (transaction, parent, referenceItem, 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} */ (c))) + left = new Item(createID(ownClientId, getState(store, ownClientId)), left, left && left.lastId, right, right && right.id, parent, null, new ContentLink(/** @type {WeakLink} */ (c), null)) left.integrate(transaction, 0) break default: @@ -856,7 +856,7 @@ export const typeMapSet = (transaction, parent, key, value) => { content = new ContentDoc(/** @type {Doc} */ (value)) break case WeakLink: - content = new ContentLink(/** @type {WeakLink} */ (value)) + content = new ContentLink(/** @type {WeakLink} */ (value), null) break; default: if (value instanceof AbstractType) { diff --git a/src/types/WeakLink.js b/src/types/WeakLink.js index cb705a9d..2029b01c 100644 --- a/src/types/WeakLink.js +++ b/src/types/WeakLink.js @@ -8,14 +8,10 @@ import { typeMapGet } from "./AbstractType.js" */ export class WeakLink { /** - * @param {AbstractType} source - * @param {Item|GC} item - * @param {string|null} key + * @param {{parent:AbstractType,item:Item|GC|null,key:string|null}|null} source */ - constructor(source, item, key) { + constructor(source) { this.source = source - this.item = item - this.key = key } /** @@ -24,14 +20,37 @@ export class WeakLink { * @return {T|undefined} */ deref() { - if (this.key) { - return /** @type {T|undefined} */ (typeMapGet(this.source, this.key)) + const item = this.linkedItem() + if (item !== undefined && !item.deleted) { + return /** @type {Item} */ (item).content.getContent()[0] } else { - if (this.item.constructor === Item) { - return this.item.content.getContent()[0] - } else { - return undefined - } + return undefined } } + + /** + * Returns currently linked item to an underlying value existing somewhere on in the document. + * + * @return {Item|GC|undefined} + */ + linkedItem() { + if (this.source !== null) { + const source = this.source + if (source.key !== null) { + return source.parent._map.get(source.key) + } else if (source.item !== null && !source.item.deleted) { + return source.item + } + } + return undefined + } + + /** + * Checks if a linked content source has been deleted. + * + * @return {boolean} + */ + get deleted() { + return this.source === null || this.source.item === null || this.source.item.deleted + } } diff --git a/tests/weakLinks.tests.js b/tests/weakLinks.tests.js index 90a81ecb..c5fd4f25 100644 --- a/tests/weakLinks.tests.js +++ b/tests/weakLinks.tests.js @@ -64,8 +64,6 @@ export const testUpdate = tc => { l1 = /** @type {Y.Map} */ (link1.deref()) l0 = /** @type {Y.Map} */ (link0.deref()) t.compare(l1.get('a2'), l0.get('a2')) - - compare(users) } /** @@ -78,19 +76,23 @@ export const testDeleteWeakLink = tc => { map0.set('b', link0) testConnector.flushAllMessages() + const link1 = /** @type {Y.WeakLink} */ map1.get('b') + const l1 = /** @type {Y.Map} */ (link1.deref()) const l0 = /** @type {Y.Map} */ (link0.deref()) - t.compare(link1.ref.get('a1'), l0.get('a1')) + t.compare(l1.get('a1'), l0.get('a1')) + t.compare(link0.deleted, false) + t.compare(link1.deleted, false) map1.delete('b') // delete links testConnector.flushAllMessages() // since links have been deleted, they no longer refer to any content + t.compare(link0.deleted, true) + t.compare(link1.deleted, true) t.compare(link0.deref(), undefined) t.compare(link1.deref(), undefined) - - compare(users) } /** @@ -106,6 +108,8 @@ export const testDeleteSource = tc => { const link1 = /** @type {Y.WeakLink>} */ (map1.get('b')) let l1 = /** @type {Y.Map} */ (link1.deref()) let l0 = /** @type {Y.Map} */ (link0.deref()) + t.compare(link0.deleted, false) + t.compare(link1.deleted, false) t.compare(l1.get('a1'), l0.get('a1')) map1.delete('a') // delete source of the link @@ -113,10 +117,10 @@ export const testDeleteSource = tc => { testConnector.flushAllMessages() // since source have been deleted, links no longer refer to any content + t.compare(link0.deleted, true) + t.compare(link1.deleted, true) t.compare(link0.deref(), undefined) t.compare(link1.deref(), undefined) - - compare(users) } /**