fixed YArray

This commit is contained in:
Kevin Jahns
2019-03-29 01:02:44 +01:00
parent d9ab593b07
commit ff981a8697
25 changed files with 761 additions and 780 deletions

View File

@@ -3,6 +3,8 @@ import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import { StructStore, getItemRange } from './StructStore.js' // eslint-disable-line
import { Transaction } from './Transaction.js' // eslint-disable-line
import * as error from 'lib0/error.js'
import { ID } from './ID.js'
class DeleteItem {
/**
@@ -38,6 +40,15 @@ export class DeleteSet {
}
}
/**
* @param {DeleteSet} ds
* @param {ID} id
* @return {boolean}
*/
export const isDeleted = (ds, id) => {
}
/**
* @param {DeleteSet} ds
*/
@@ -70,7 +81,7 @@ export const sortAndMergeDeleteSet = ds => {
export const createDeleteSetFromTransaction = transaction => {
const ds = new DeleteSet()
transaction.deleted.forEach(item => {
map.setTfUndefined(ds.clients, item.id.client, () => []).push(new DeleteItem(item.id.clock, item.length))
map.setIfUndefined(ds.clients, item.id.client, () => []).push(new DeleteItem(item.id.clock, item.length))
})
sortAndMergeDeleteSet(ds)
return ds

23
src/utils/Snapshot.js Normal file
View File

@@ -0,0 +1,23 @@
import { DeleteSet, isDeleted } from './DeleteSet'
import { AbstractItem } from '../structs/AbstractItem.js' // eslint-disable-line
export class Snapshot {
/**
* @param {DeleteSet} ds delete store
* @param {Map<number,number>} sm state map
* @param {Map<number,string>} userMap
*/
constructor (ds, sm, userMap) {
this.ds = new DeleteSet()
this.sm = sm
this.userMap = userMap
}
}
/**
* @param {AbstractItem} item
* @param {Snapshot} [snapshot]
*/
export const isVisible = (item, snapshot) => snapshot === undefined ? !item._deleted : (
snapshot.sm.has(item.id.client) && (snapshot.sm.get(item.id.client) || 0) > item.id.clock && !isDeleted(snapshot.ds, item._id)
)

View File

@@ -31,6 +31,19 @@ export const getStates = store =>
}
})
/**
* @param {StructStore} store
* @param {number} client
*/
export const getState = (store, client) => {
const structs = store.clients.get(client)
if (structs === undefined) {
return 0
}
const lastStruct = structs[structs.length - 1]
return lastStruct.id.clock + lastStruct.length
}
/**
* @param {StructStore} store
*/
@@ -51,7 +64,7 @@ export const integretyCheck = store => {
* @param {AbstractStruct} struct
*/
export const addStruct = (store, struct) => {
map.setTfUndefined(store.clients, struct.id.client, () => []).push(struct)
map.setIfUndefined(store.clients, struct.id.client, () => []).push(struct)
}
/**
@@ -107,21 +120,6 @@ const find = (store, id) => {
// @ts-ignore
export const getItemType = (store, id) => find(store, id)
/**
* @param {Transaction} transaction
* @param {AbstractItem} struct
* @param {number} diff
*/
const splitStruct = (transaction, struct, diff) => {
const right = struct.splitAt(diff)
if (transaction.added.has(struct)) {
transaction.added.add(right)
} else if (transaction.deleted.has(struct)) {
transaction.deleted.add(right)
}
return right
}
/**
* Expects that id is actually in store. This function throws or is an infinite loop otherwise.
* @param {StructStore} store
@@ -139,10 +137,11 @@ export const getItemCleanStart = (store, transaction, id) => {
const structs = store.clients.get(id.client)
const index = findIndex(structs, id.clock)
/**
* @type {any}
* @type {AbstractItem}
*/
let struct = structs[index]
if (struct.id.clock < id.clock) {
struct.splitAt()
struct = splitStruct(transaction, struct, id.clock - struct.id.clock)
structs.splice(index, 0, struct)
}
@@ -213,11 +212,11 @@ export const getItemRange = (store, transaction, client, clock, len) => {
* @param {AbstractStruct} struct
* @param {AbstractStruct} newStruct
*/
export const replace = (store, struct, newStruct) => {
export const replaceStruct = (store, struct, newStruct) => {
/**
* @type {Array<AbstractStruct>}
*/
// @ts-ignore
const structs = store.clients.get(struct.id.client)
structs[findIndex(structs, struct.id)] = newStruct
structs[findIndex(structs, struct.id.clock)] = newStruct
}

View File

@@ -8,8 +8,10 @@ import { AbstractItem } from '../structs/AbstractItem.js' // eslint-disable-line
import { Y } from './Y.js' // eslint-disable-line
import { YEvent } from './YEvent.js' // eslint-disable-line
import { ItemType } from '../structs/ItemType.js' // eslint-disable-line
import { getState } from './StructStore.js'
import { writeStructsFromTransaction } from './structEncoding.js'
import { createID } from './ID.js' // eslint-disable-line
import { createDeleteSetFromTransaction, writeDeleteSet } from './DeleteSet.js'
import { getState } from './StructStore.js'
/**
* A transaction is created for every change on the Yjs model. It is possible
@@ -54,26 +56,41 @@ export class Transaction {
*/
this.deleted = new Set()
/**
* Saves the old state set of the Yjs instance. If a state was modified,
* the original value is saved here.
* If a state was modified, the original value is saved here.
* Use `stateUpdates` to compute the original state before the transaction,
* or to compute the set of inserted operations.
* @type {Map<Number,Number>}
*/
this.beforeState = new Map()
this.stateUpdates = new Map()
/**
* All types that were directly modified (property added or child
* inserted/deleted). New types are not included in this Set.
* Maps from type to parentSubs (`item._parentSub = null` for YArray)
* @type {Map<ItemType,Set<String|null>>}
* @type {Map<AbstractType,Set<String|null>>}
*/
this.changed = new Map()
/**
* Stores the events for the types that observe also child elements.
* It is mainly used by `observeDeep`.
* @type {Map<ItemType,Array<YEvent>>}
* @type {Map<AbstractType,Array<YEvent>>}
*/
this.changedParentTypes = new Map()
this.encodedStructsLen = 0
this.encodedStructs = encoding.createEncoder()
/**
* @type {encoding.Encoder|null}
*/
this._updateMessage = null
}
/**
* @type {encoding.Encoder}
*/
get updateMessage () {
if (this._updateMessage === null) {
const encoder = encoding.createEncoder()
writeStructsFromTransaction(encoder, this)
writeDeleteSet(encoder, createDeleteSetFromTransaction(this))
this._updateMessage = encoder
}
return this._updateMessage
}
}

View File

@@ -1,6 +1,3 @@
import { DeleteStore } from './DeleteSet.js/index.js' // TODO: remove
import { OperationStore } from './OperationStore.js'
import { StateStore } from './StateStore.js'
import { StructStore } from './StructStore.js'
import * as random from 'lib0/random.js'
import * as map from 'lib0/map.js'
@@ -8,22 +5,10 @@ import { Observable } from 'lib0/observable.js'
import { Transaction } from './Transaction.js'
import { AbstractStruct, AbstractRef } from '../structs/AbstractStruct.js' // eslint-disable-line
import { AbstractType } from '../types/AbstractType.js'
import { YArray } from '../types/YArray.js'
/**
* Anything that can be encoded with `JSON.stringify` and can be decoded with
* `JSON.parse`.
*
* The following property should hold:
* `JSON.parse(JSON.stringify(key))===key`
*
* At the moment the only safe values are number and string.
*
* @typedef {(number|string|Object)} encodable
*/
/**
* A Yjs instance handles the state of shared data.
* @extends Observable<string>
*/
export class Y extends Observable {
/**
@@ -33,6 +18,9 @@ export class Y extends Observable {
super()
this.gcEnabled = conf.gc || false
this.clientID = random.uint32()
/**
* @type {Map<string, AbstractType>}
*/
this.share = new Map()
this.store = new StructStore()
/**
@@ -65,7 +53,7 @@ export class Y extends Observable {
* that happened inside of the transaction are sent as one message to the
* other peers.
*
* @param {Function} f The function that should be executed as a transaction
* @param {function(Transaction):void} f The function that should be executed as a transaction
* @param {?Boolean} remote Optional. Whether this transaction is initiated by
* a remote peer. This should not be set manually!
* Defaults to false.
@@ -78,7 +66,7 @@ export class Y extends Observable {
this.emit('beforeTransaction', [this, this._transaction, remote])
}
try {
f(this)
f(this._transaction)
} catch (e) {
console.error(e)
}
@@ -88,7 +76,7 @@ export class Y extends Observable {
this._transaction = null
// emit change events on changed types
transaction.changed.forEach((subs, itemtype) => {
if (!itemtype._deleted) {
if (!itemtype._item.deleted) {
itemtype.type._callObserver(transaction, subs, remote)
}
})

View File

@@ -1,22 +0,0 @@
import { DeleteStore } from './DeleteSet'
export class HistorySnapshot {
/**
* @param {DeleteStore} ds delete store
* @param {Map<number,number>} sm state map
* @param {Map<number,string>} userMap
*/
constructor (ds, sm, userMap) {
this.ds = new DeleteStore()
this.sm = sm
this.userMap = userMap
}
}
/**
* @param {Item} item
* @param {HistorySnapshot} [snapshot]
*/
export const isVisible = (item, snapshot) => snapshot === undefined ? !item._deleted : (
snapshot.sm.has(item._id.user) && (snapshot.sm.get(item._id.user) || 0) > item._id.clock && !snapshot.ds.isDeleted(item._id)
)

View File

@@ -1,13 +1,10 @@
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
import { getStructReference } from './structReferences.js'
import { AbstractStruct, AbstractRef } from '../structs/AbstractStruct.js'
import { ID, createID, writeID, writeNullID } from './ID.js'
import * as binary from 'lib0/binary.js'
export const writeStructToTransaction = (transaction, struct) => {
transaction.encodedStructsLen++
struct._toBinary(transaction.encodedStructs)
}
import { Transaction } from './Transaction.js'
import { findIndex } from './StructStore.js'
const structRefs = [
ItemBinaryRef
@@ -18,7 +15,6 @@ const structRefs = [
*
* This is called when data is received from a remote peer.
*
* @param {Y} y The Yjs instance that this Item belongs to.
* @param {decoding.Decoder} decoder The decoder object to read data from.
* @return {AbstractRef}
*
@@ -28,3 +24,23 @@ export const read = decoder => {
const info = decoding.readUint8(decoder)
return new structRefs[binary.BITS5 & info](decoder, info)
}
/**
* @param {encoding.Encoder} encoder
* @param {Transaction} transaction
*/
export const writeStructsFromTransaction = (encoder, transaction) => {
const stateUpdates = transaction.stateUpdates
const y = transaction.y
encoding.writeVarUint(encoder, stateUpdates.size)
stateUpdates.forEach((clock, client) => {
/**
* @type {Array<AbstractStruct>}
*/
// @ts-ignore
const structs = y.store.clients.get(client)
for (let i = findIndex(structs, clock); i < structs.length; i++) {
structs[i].write(encoder, 0)
}
})
}